Skip to content
LAM
Read Home Blog
Make Projects HTML Tools Games
Touch grass Notes Resume Links
Home Blog HTML Projects
Tools Games Notes Resume Links
Back draw2Video Software
Download Open
Show description 417 chars · Software

draw2Video

draw2Video





































draw2Video

Your Imagination, Animated.



×





Generate Image from Drawing

Your drawing will be used as a guide. Describe any extra details.


Generate Image

Image will appear here








Generate Video Storyboard

Describe a scene or action. The AI will create a 4-frame storyboard.


Generate Storyboard

draw2Video

19,881 bytes · HTML source
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>draw2Video</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Inter', sans-serif;
            overscroll-behavior: none;
        }
        .dot-grid {
            background-image: radial-gradient(circle, #e2e8f0 1px, rgba(0, 0, 0, 0) 1px);
            background-size: 20px 20px;
        }
        #studio-sidebar {
            transition: transform 0.3s ease-in-out;
            transform: translateX(100%);
            box-shadow: -10px 0 20px -10px rgba(0,0,0,0.1);
        }
        #studio-sidebar.open {
            transform: translateX(0);
        }
        .tool-btn.active {
            background-color: #3b82f6;
            color: white;
        }
        .loader {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #3498db;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            animation: spin 1s linear infinite;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        .storyboard-frame {
            cursor: pointer;
            border: 4px solid transparent;
            transition: border-color 0.2s ease-in-out;
        }
        .storyboard-frame[data-selected="true"] {
            border-color: #3b82f6;
        }
    </style>
</head>
<body class="bg-slate-200 m-0 overflow-hidden flex h-screen">

    <!-- Main Content Area -->
    <main class="flex-1 relative bg-slate-100 h-full">
        <!-- Canvas for drawing -->
        <canvas id="drawingCanvas" class="w-full h-full dot-grid cursor-crosshair"></canvas>

        <!-- Drawing Toolbar -->
        <div id="toolbar" class="fixed bottom-5 left-1/2 -translate-x-1/2 bg-white/80 backdrop-filter backdrop-blur-lg border border-slate-300 rounded-xl p-2 flex items-center gap-4 z-20">
            <button id="penTool" class="tool-btn p-3 rounded-lg hover:bg-slate-200 transition-colors active">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
            </button>
            <button id="eraserTool" class="tool-btn p-3 rounded-lg hover:bg-slate-200 transition-colors">
                 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21H7Z"/><path d="M22 21H7"/><path d="m5 12 5 5"/></svg>
            </button>
            <div class="w-px h-8 bg-slate-300 mx-1"></div>
            <input type="color" id="colorPicker" value="#000000" class="w-10 h-10 p-0 border-none bg-transparent rounded-lg cursor-pointer appearance-none">
        </div>
        
        <!-- AI Studio Toggle Button -->
        <button id="studio-toggle" class="fixed top-4 right-4 bg-blue-500 hover:bg-blue-600 text-white rounded-full p-3 shadow-lg z-20 transition-transform hover:scale-110">
            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 5 4 4"/><path d="M12 21a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3Z"/><path d="M18 12h3"/><path d="M21 9V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6"/><path d="M3 10h4"/><path d="M3 14h4"/></svg>
        </button>
    </main>

    <!-- AI Studio Sidebar -->
    <aside id="studio-sidebar" class="w-96 bg-white/90 backdrop-filter backdrop-blur-lg border-l border-slate-300 flex flex-col h-screen absolute right-0 top-0 z-30">
        <div class="p-4 border-b border-slate-300 flex justify-between items-center">
            <div>
                <h2 class="text-xl font-bold text-slate-800">draw2Video</h2>
                <p class="text-sm text-slate-500">Your Imagination, Animated.</p>
            </div>
            <button id="studio-close" class="text-slate-500 hover:text-slate-800 text-2xl font-bold">&times;</button>
        </div>
        <div class="flex-1 overflow-y-auto p-4 space-y-6">
            <!-- Image Generation Section -->
            <div>
                <h3 class="font-semibold text-slate-700 mb-2">Generate Image from Drawing</h3>
                <p class="text-sm text-slate-500 mb-2">Your drawing will be used as a guide. Describe any extra details.</p>
                <textarea id="image-prompt" class="w-full p-2 border border-slate-300 rounded-md" rows="3" placeholder="e.g., a photorealistic cat, fantasy style, vibrant colors..."></textarea>
                <button id="generate-image-btn" class="w-full mt-2 bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded-lg transition-colors">Generate Image</button>
                <div id="image-output" class="mt-4 bg-slate-100 rounded-md aspect-square flex items-center justify-center">
                    <p class="text-slate-400">Image will appear here</p>
                </div>
            </div>

            <!-- Video Generation Section -->
            <div>
                <h3 class="font-semibold text-slate-700 mb-2">Generate Video Storyboard</h3>
                 <p class="text-sm text-slate-500 mb-2">Describe a scene or action. The AI will create a 4-frame storyboard.</p>
                <textarea id="video-prompt" class="w-full p-2 border border-slate-300 rounded-md" rows="3" placeholder="e.g., a car driving through a neon city at night..."></textarea>
                <button id="generate-storyboard-btn" class="w-full mt-2 bg-purple-500 hover:bg-purple-600 text-white font-bold py-2 px-4 rounded-lg transition-colors">Generate Storyboard</button>
                <div id="storyboard-output" class="mt-4 grid grid-cols-2 gap-2">
                   <!-- Storyboard frames will be injected here -->
                </div>
                <div id="video-generation-controls" class="mt-4">
                    <!-- This will be populated dynamically -->
                </div>
            </div>
        </div>
    </aside>

    <script type="module">
        // --- Canvas & Drawing Setup ---
        const canvas = document.getElementById('drawingCanvas');
        const ctx = canvas.getContext('2d');
        let isDrawing = false;
        let tool = 'pen';
        let strokeColor = '#000000';

        function resizeCanvas() {
            canvas.width = canvas.offsetWidth;
            canvas.height = canvas.offsetHeight;
        }

        function getCoords(e) {
            const rect = canvas.getBoundingClientRect();
            const touch = e.touches ? e.touches[0] : e;
            return { x: touch.clientX - rect.left, y: touch.clientY - rect.top };
        }

        function startDrawing(e) { e.preventDefault(); isDrawing = true; const { x, y } = getCoords(e); ctx.beginPath(); ctx.moveTo(x, y); }
        function draw(e) { if (!isDrawing) return; e.preventDefault(); const { x, y } = getCoords(e); ctx.lineTo(x, y); ctx.strokeStyle = tool === 'pen' ? strokeColor : '#f1f5f9'; ctx.lineWidth = tool === 'pen' ? 5 : 30; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.stroke(); }
        function stopDrawing() { isDrawing = false; ctx.closePath(); }

        // --- UI Element Listeners ---
        document.getElementById('penTool').addEventListener('click', () => { tool = 'pen'; document.getElementById('penTool').classList.add('active'); document.getElementById('eraserTool').classList.remove('active'); });
        document.getElementById('eraserTool').addEventListener('click', () => { tool = 'eraser'; document.getElementById('eraserTool').classList.add('active'); document.getElementById('penTool').classList.remove('active'); });
        document.getElementById('colorPicker').addEventListener('input', (e) => { strokeColor = e.target.value; });

        const studioSidebar = document.getElementById('studio-sidebar');
        document.getElementById('studio-toggle').addEventListener('click', () => studioSidebar.classList.add('open'));
        document.getElementById('studio-close').addEventListener('click', () => studioSidebar.classList.remove('open'));

        // --- AI Studio API Logic ---
        const imageOutput = document.getElementById('image-output');
        const storyboardOutput = document.getElementById('storyboard-output');
        const videoGenerationControls = document.getElementById('video-generation-controls');
        const generateImageBtn = document.getElementById('generate-image-btn');
        const generateStoryboardBtn = document.getElementById('generate-storyboard-btn');
        let selectedFrames = [];

        const apiKey = ""; // Leave blank, will be handled by environment

        function showLoader(element) { element.innerHTML = '<div class="loader mx-auto"></div>'; }
        
        async function generateImageWithImagen(prompt, outputElement) {
             const payload = { instances: [{ prompt: prompt }], parameters: { "sampleCount": 1 } };
             try {
                const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/imagen-3.0-generate-002:predict?key=${apiKey}`;
                const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
                if (!response.ok) throw new Error(`API Error: ${response.status}`);
                const result = await response.json();
                if (result.predictions && result.predictions[0].bytesBase64Encoded) {
                    const imageUrl = `data:image/png;base64,${result.predictions[0].bytesBase64Encoded}`;
                    outputElement.innerHTML = `<img src="${imageUrl}" class="w-full h-full object-cover rounded-md">`;
                    return { prompt, imageUrl }; 
                } else {
                     outputElement.innerHTML = `<p class="text-red-500 p-4">Error: Could not generate image from prompt.</p>`;
                     return null;
                }
            } catch (error) {
                console.error('Error calling Imagen API:', error);
                outputElement.innerHTML = `<p class="text-red-500 p-4">Error: Imagen API call failed.</p>`;
                return null;
            }
        }

        generateImageBtn.addEventListener('click', async () => {
            const promptText = document.getElementById('image-prompt').value;
            if (!promptText) { alert("Please enter a prompt to describe the image."); return; }
            showLoader(imageOutput);
            const drawingDataUrl = canvas.toDataURL('image/png');
            const base64ImageData = drawingDataUrl.split(',')[1];
            const payload = {
                contents: [{ role: "user", parts: [ { text: `Based on the user's drawing and their prompt, create a new, detailed, single-sentence text-to-image prompt to generate a beautiful image. User's prompt: "${promptText}"` }, { inlineData: { mimeType: "image/png", data: base64ImageData } } ] }]
            };
            try {
                const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
                const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
                if (!response.ok) throw new Error(`Gemini API Error: ${response.status}`);
                const result = await response.json();
                if (result.candidates && result.candidates[0].content.parts[0].text) {
                    const imagePrompt = result.candidates[0].content.parts[0].text;
                    await generateImageWithImagen(imagePrompt, imageOutput);
                } else { throw new Error("Gemini did not return a valid prompt."); }
            } catch (error) {
                console.error('Error in image generation flow:', error);
                imageOutput.innerHTML = `<p class="text-red-500 p-4">Error: ${error.message}</p>`;
            }
        });

        generateStoryboardBtn.addEventListener('click', async () => {
            const promptText = document.getElementById('video-prompt').value;
            if (!promptText) { alert("Please enter a prompt to describe the video scene."); return; }
            storyboardOutput.innerHTML = '';
            videoGenerationControls.innerHTML = '';
            selectedFrames = [];
            showLoader(storyboardOutput);
            const storyboardPrompt = `Based on the user's request for a video scene, create 4 distinct text-to-image prompts that would form a cohesive storyboard. The user's request is: "${promptText}". Return ONLY a JSON array of 4 strings. Example: ["prompt 1", "prompt 2", "prompt 3", "prompt 4"]`;
            const payload = { contents: [{ role: "user", parts: [{ text: storyboardPrompt }] }], generationConfig: { responseMimeType: "application/json" } };
            try {
                const geminiApiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
                const response = await fetch(geminiApiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
                const result = await response.json();
                if (result.candidates && result.candidates[0].content.parts[0].text) {
                    const prompts = JSON.parse(result.candidates[0].content.parts[0].text);
                    storyboardOutput.innerHTML = '';
                    for (const prompt of prompts) {
                        const frameDiv = document.createElement('div');
                        frameDiv.className = 'storyboard-frame bg-slate-100 rounded-md aspect-square flex items-center justify-center';
                        frameDiv.dataset.selected = "false";
                        storyboardOutput.appendChild(frameDiv);
                        showLoader(frameDiv);
                        const generatedData = await generateImageWithImagen(prompt, frameDiv);
                        if(generatedData) {
                            frameDiv.dataset.prompt = generatedData.prompt;
                            frameDiv.dataset.imageUrl = generatedData.imageUrl;
                            frameDiv.addEventListener('click', () => toggleFrameSelection(frameDiv));
                        }
                    }
                } else { throw new Error("Could not generate storyboard prompts."); }
            } catch (error) {
                console.error('Error generating storyboard:', error);
                storyboardOutput.innerHTML = `<p class="text-red-500 p-4">Error: Storyboard generation failed.</p>`;
            }
        });

        function toggleFrameSelection(frameDiv) {
            const isSelected = frameDiv.dataset.selected === 'true';
            if (isSelected) {
                frameDiv.dataset.selected = 'false';
                selectedFrames = selectedFrames.filter(f => f.prompt !== frameDiv.dataset.prompt);
            } else {
                if (selectedFrames.length < 2) {
                    frameDiv.dataset.selected = 'true';
                    selectedFrames.push({ prompt: frameDiv.dataset.prompt, imageUrl: frameDiv.dataset.imageUrl });
                }
            }
            updateVideoControls();
        }

        function updateVideoControls() {
            videoGenerationControls.innerHTML = '';
            if (selectedFrames.length === 2) {
                const btn = document.createElement('button');
                btn.id = 'generate-video-from-selection-btn';
                btn.className = 'w-full mt-2 bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg transition-colors';
                btn.textContent = 'Generate Video from Selection';
                btn.onclick = generateVideoFromFrames;
                videoGenerationControls.appendChild(btn);
            }
        }

        async function generateVideoFromFrames() {
            if (selectedFrames.length !== 2) return;
            const [frame1, frame2] = selectedFrames;
            
            videoGenerationControls.innerHTML = '';
            const videoResultDiv = document.createElement('div');
            videoResultDiv.className = 'mt-4 space-y-2';
            const title = document.createElement('h4');
            title.className = 'font-semibold text-slate-700';
            title.textContent = 'Generating Video...';
            videoResultDiv.appendChild(title);
            
            const loaderContainer = document.createElement('div');
            loaderContainer.className = 'bg-slate-100 rounded-md aspect-video flex flex-col items-center justify-center p-4';
            showLoader(loaderContainer);
            const loaderText = document.createElement('p');
            loaderText.className = 'text-sm text-slate-500 mt-2';
            loaderText.textContent = 'Sending frames to video model...';
            loaderContainer.appendChild(loaderText);

            videoResultDiv.appendChild(loaderContainer);
            videoGenerationControls.appendChild(videoResultDiv);

            console.log("Simulating video generation request with:");
            console.log("Start Frame:", frame1.prompt);
            console.log("End Frame:", frame2.prompt);
            
            setTimeout(() => {
                title.textContent = 'Video Generation Complete (Simulation)';
                loaderContainer.innerHTML = `
                    <div class="relative w-full h-full bg-black rounded-md">
                        <img src="${frame1.imageUrl}" class="w-full h-full object-cover rounded-md opacity-80" alt="Video thumbnail">
                        <div class="absolute inset-0 flex items-center justify-center">
                            <svg class="w-16 h-16 text-white/80" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path></svg>
                        </div>
                        <p class="absolute bottom-2 left-2 text-white text-xs bg-black/50 px-2 py-1 rounded">This is a simulated video output.</p>
                    </div>
                `;
            }, 5000); 
        }

        // --- Initialization ---
        window.addEventListener('load', resizeCanvas);
        window.addEventListener('resize', resizeCanvas);
        canvas.addEventListener('mousedown', startDrawing);
        canvas.addEventListener('mousemove', draw);
        canvas.addEventListener('mouseup', stopDrawing);
        canvas.addEventListener('mouseout', stopDrawing);
        canvas.addEventListener('touchstart', startDrawing, { passive: false });
        canvas.addEventListener('touchmove', draw, { passive: false });
        canvas.addEventListener('touchend', stopDrawing);
    </script>
</body>
</html>