sketchmaster-pro / index.html
UnSinnlos's picture
jetzt klappt noch nicht einmal mehr das reinladen des bildes !!
2f8ce9c verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SketchMaster Pro - Image to Sketch Converter</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
<style>
.file-input-label:hover .upload-icon {
transform: translateY(-3px);
transition: all 0.2s ease;
}
.preview-container {
min-height: 400px;
background: linear-gradient(135deg, #f5f7fa 0%, #e4e8eb 100%);
}
.parameter-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #4f46e5;
cursor: pointer;
}
.dropdown:hover .dropdown-menu {
display: block;
}
</style>
</head>
<body class="bg-gray-50">
<div id="vanta-bg" class="fixed inset-0 -z-10 opacity-20"></div>
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header -->
<header class="mb-12 text-center">
<h1 class="text-4xl md:text-5xl font-bold text-gray-800 mb-2">SketchMaster Pro</h1>
<p class="text-xl text-gray-600">Transform your photos into beautiful hand-drawn sketches</p>
</header>
<!-- Main Content -->
<div class="bg-white rounded-xl shadow-xl overflow-hidden">
<div class="grid md:grid-cols-2 gap-8 p-6">
<!-- Upload Section -->
<div class="space-y-6">
<div class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center preview-container flex items-center justify-center">
<div id="preview-content" class="text-center">
<i data-feather="upload" class="w-12 h-12 mx-auto text-gray-400 mb-4"></i>
<label for="image-upload" class="file-input-label cursor-pointer">
<span class="inline-flex items-center px-6 py-3 bg-indigo-600 text-white font-medium rounded-lg hover:bg-indigo-700 transition">
<i data-feather="upload" class="upload-icon mr-2 w-5 h-5"></i>
Upload Image
</span>
</label>
<input type="file" id="image-upload" accept="image/*" class="hidden">
<p class="mt-2 text-sm text-gray-500">or drag and drop</p>
</div>
<img id="image-preview" class="hidden max-h-full max-w-full rounded-lg" alt="Preview">
</div>
<div class="space-y-4">
<div>
<h3 class="font-medium text-gray-700 mb-2">Edge Detection</h3>
<div class="grid grid-cols-3 gap-2">
<button class="edge-method-btn active bg-indigo-100 text-indigo-800 border border-indigo-200 px-3 py-2 rounded-lg text-sm font-medium">Canny</button>
<button class="edge-method-btn bg-gray-100 text-gray-800 border border-gray-200 px-3 py-2 rounded-lg text-sm font-medium">Sobel</button>
<button class="edge-method-btn bg-gray-100 text-gray-800 border border-gray-200 px-3 py-2 rounded-lg text-sm font-medium">Laplace</button>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="blur-slider" class="block text-sm font-medium text-gray-700 mb-1">Blur Amount</label>
<input type="range" id="blur-slider" min="0" max="5" step="0.1" value="1.2" class="w-full parameter-slider">
<div class="flex justify-between text-xs text-gray-500">
<span>0</span>
<span>5</span>
</div>
</div>
<div>
<label for="thickness-slider" class="block text-sm font-medium text-gray-700 mb-1">Line Thickness</label>
<input type="range" id="thickness-slider" min="1" max="10" step="1" value="2" class="w-full parameter-slider">
<div class="flex justify-between text-xs text-gray-500">
<span>1</span>
<span>10</span>
</div>
</div>
</div>
</div>
</div>
<!-- Parameters Section -->
<div class="space-y-6">
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-700 mb-3">Threshold Options</h3>
<div class="grid grid-cols-2 gap-4">
<div class="relative">
<button id="threshold-btn" class="w-full flex justify-between items-center px-4 py-2 bg-white border border-gray-300 rounded-lg shadow-sm text-left">
<span>Otsu</span>
<i data-feather="chevron-down" class="w-4 h-4"></i>
</button>
<div id="threshold-menu" class="hidden absolute z-10 mt-1 w-full bg-white border border-gray-200 rounded-lg shadow-lg">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Otsu</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Fixed</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Adaptive</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Posterize</a>
</div>
</div>
<div id="threshold-params" class="space-y-2">
<div class="threshold-option" data-method="fixed">
<label class="block text-xs text-gray-500">Threshold Value</label>
<input type="range" min="0" max="255" value="128" class="w-full parameter-slider">
</div>
<div class="threshold-option hidden" data-method="adaptive">
<label class="block text-xs text-gray-500">Block Size</label>
<input type="range" min="3" max="51" step="2" value="25" class="w-full parameter-slider">
</div>
<div class="threshold-option hidden" data-method="posterize">
<label class="block text-xs text-gray-500">Levels</label>
<div class="flex space-x-2">
<button class="posterize-level active px-3 py-1 bg-indigo-100 text-indigo-800 rounded-full text-xs">2</button>
<button class="posterize-level px-3 py-1 bg-gray-100 text-gray-800 rounded-full text-xs">3</button>
<button class="posterize-level px-3 py-1 bg-gray-100 text-gray-800 rounded-full text-xs">4</button>
<button class="posterize-level px-3 py-1 bg-gray-100 text-gray-800 rounded-full text-xs">5</button>
<button class="posterize-level px-3 py-1 bg-gray-100 text-gray-800 rounded-full text-xs">6</button>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="flex items-center justify-between mb-3">
<h3 class="font-medium text-gray-700">Hatching Effect</h3>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="hatch-toggle" class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div>
</label>
</div>
<div id="hatch-params" class="space-y-3 hidden">
<div>
<label class="block text-xs text-gray-500">Angle</label>
<input type="range" min="0" max="180" value="45" class="w-full parameter-slider">
</div>
<div>
<label class="block text-xs text-gray-500">Spacing</label>
<input type="range" min="2" max="20" value="8" class="w-full parameter-slider">
</div>
<div>
<label class="block text-xs text-gray-500">Thickness</label>
<input type="range" min="1" max="5" value="1" class="w-full parameter-slider">
</div>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="flex items-center">
<input id="invert-toggle" type="checkbox" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
<label for="invert-toggle" class="ml-2 block text-sm text-gray-700">Invert Colors</label>
</div>
<div class="flex items-center">
<input id="jitter-toggle" type="checkbox" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" checked>
<label for="jitter-toggle" class="ml-2 block text-sm text-gray-700">Enable Jitter</label>
</div>
</div>
<button id="process-btn" class="w-full py-3 px-4 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition flex items-center justify-center">
<i data-feather="zap" class="w-5 h-5 mr-2"></i>
Generate Sketch
</button>
</div>
</div>
<!-- Results Section -->
<div id="results-section" class="hidden border-t border-gray-200 p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-800">Your Sketch Results</h2>
<button id="download-btn" class="flex items-center text-indigo-600 hover:text-indigo-800">
<i data-feather="download" class="w-4 h-4 mr-1"></i>
<span>Download</span>
</button>
</div>
<div class="grid md:grid-cols-2 gap-6">
<div class="border rounded-lg overflow-hidden">
<h3 class="bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700">Original</h3>
<div class="p-4">
<img id="original-display" src="" alt="Original Image" class="max-h-80 mx-auto">
</div>
</div>
<div class="border rounded-lg overflow-hidden">
<h3 class="bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700">Sketch</h3>
<div class="p-4">
<img id="result-display" src="" alt="Sketch Result" class="max-h-80 mx-auto">
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="mt-12 text-center text-gray-500 text-sm">
<p>SketchMaster Pro - Transform your images into artistic sketches with ease</p>
</footer>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/opencv.js/4.5.5/opencv.js" onload="onOpenCvReady();" async></script>
<script>
function onOpenCvReady() {
console.log('OpenCV.js is ready');
}
// Initialize Vanta.js background
VANTA.GLOBE({
el: "#vanta-bg",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: 0x4f46e5,
backgroundColor: 0xf8fafc,
size: 0.7
});
// Initialize Feather Icons
document.addEventListener('DOMContentLoaded', function() {
feather.replace();
// File upload handling
const fileInput = document.getElementById('image-upload');
const previewContent = document.getElementById('preview-content');
const imagePreview = document.getElementById('image-preview');
const previewContainer = document.querySelector('.preview-container');
fileInput.addEventListener('change', function(e) {
if (e.target.files.length) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = function(event) {
imagePreview.src = event.target.result;
imagePreview.classList.remove('hidden');
previewContent.classList.add('hidden');
};
reader.readAsDataURL(file);
}
});
// Drag and drop handling
previewContainer.addEventListener('dragover', function(e) {
e.preventDefault();
this.classList.add('border-indigo-500', 'bg-indigo-50');
});
previewContainer.addEventListener('dragleave', function() {
this.classList.remove('border-indigo-500', 'bg-indigo-50');
});
previewContainer.addEventListener('drop', function(e) {
e.preventDefault();
this.classList.remove('border-indigo-500', 'bg-indigo-50');
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
const event = new Event('change');
fileInput.dispatchEvent(event);
}
});
// Threshold method dropdown
const thresholdBtn = document.getElementById('threshold-btn');
const thresholdMenu = document.getElementById('threshold-menu');
thresholdBtn.addEventListener('click', function() {
thresholdMenu.classList.toggle('hidden');
});
// Close dropdown when clicking outside
document.addEventListener('click', function(e) {
if (!thresholdBtn.contains(e.target) && !thresholdMenu.contains(e.target)) {
thresholdMenu.classList.add('hidden');
}
});
// Threshold method selection
const thresholdOptions = thresholdMenu.querySelectorAll('a');
thresholdOptions.forEach(option => {
option.addEventListener('click', function(e) {
e.preventDefault();
const method = this.textContent.toLowerCase();
thresholdBtn.querySelector('span').textContent = this.textContent;
thresholdMenu.classList.add('hidden');
// Show/hide relevant parameters
document.querySelectorAll('.threshold-option').forEach(el => {
el.classList.add('hidden');
});
const activeOption = document.querySelector(`.threshold-option[data-method="${method}"]`);
if (activeOption) activeOption.classList.remove('hidden');
});
});
// Hatching toggle
const hatchToggle = document.getElementById('hatch-toggle');
const hatchParams = document.getElementById('hatch-params');
hatchToggle.addEventListener('change', function() {
if (this.checked) {
hatchParams.classList.remove('hidden');
} else {
hatchParams.classList.add('hidden');
}
});
// Edge method buttons
const edgeMethodBtns = document.querySelectorAll('.edge-method-btn');
edgeMethodBtns.forEach(btn => {
btn.addEventListener('click', function() {
edgeMethodBtns.forEach(b => {
b.classList.remove('active', 'bg-indigo-100', 'text-indigo-800', 'border-indigo-200');
b.classList.add('bg-gray-100', 'text-gray-800', 'border-gray-200');
});
this.classList.add('active', 'bg-indigo-100', 'text-indigo-800', 'border-indigo-200');
this.classList.remove('bg-gray-100', 'text-gray-800', 'border-gray-200');
});
});
// Posterize level buttons
const posterizeLevels = document.querySelectorAll('.posterize-level');
posterizeLevels.forEach(btn => {
btn.addEventListener('click', function() {
posterizeLevels.forEach(b => {
b.classList.remove('active', 'bg-indigo-100', 'text-indigo-800');
b.classList.add('bg-gray-100', 'text-gray-800');
});
this.classList.add('active', 'bg-indigo-100', 'text-indigo-800');
});
});
// Process button (simulate processing)
const processBtn = document.getElementById('process-btn');
const resultsSection = document.getElementById('results-section');
const originalDisplay = document.getElementById('original-display');
const resultDisplay = document.getElementById('result-display');
processBtn.addEventListener('click', function() {
if (!imagePreview.src) {
alert('Please upload an image first');
return;
}
// Show loading state
this.innerHTML = '<i data-feather="loader" class="w-5 h-5 mr-2 animate-spin"></i> Processing...';
feather.replace();
// Simulate processing delay
setTimeout(() => {
// Process image to sketch effect
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
// Apply sketch effect
ctx.drawImage(img, 0, 0);
// Convert to grayscale
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg;
data[i + 1] = avg;
data[i + 2] = avg;
}
// Convert to grayscale and invert
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = 255 - avg;
data[i + 1] = 255 - avg;
data[i + 2] = 255 - avg;
}
// Apply blur effect based on slider
const blurAmount = document.getElementById('blur-slider').value;
if (blurAmount > 0) {
ctx.filter = `blur(${blurAmount}px)`;
ctx.drawImage(canvas, 0, 0);
ctx.filter = 'none';
}
ctx.putImageData(imageData, 0, 0);
// Show results
originalDisplay.src = imagePreview.src;
resultDisplay.src = canvas.toDataURL('image/png');
resultsSection.classList.remove('hidden');
};
img.src = imagePreview.src;
// Reset button
this.innerHTML = '<i data-feather="zap" class="w-5 h-5 mr-2"></i> Generate Sketch';
feather.replace();
// Scroll to results
resultsSection.scrollIntoView({ behavior: 'smooth' });
}, 1500);
});
// Download button
const downloadBtn = document.getElementById('download-btn');
downloadBtn.addEventListener('click', function() {
if (!resultDisplay.src) return;
// Create download link
const link = document.createElement('a');
link.download = 'sketchmaster-result.png';
link.href = resultDisplay.src;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
});
</script>
</body>
</html>