// TiltShift Studio - Main JavaScript class TiltShiftEditor { constructor() { this.canvas = document.getElementById('previewCanvas'); this.ctx = this.canvas.getContext('2d'); this.originalImage = null; this.currentImage = null; this.filters = { focusPosition: 50, blurAmount: 10, focusSize: 30, effectType: 'linear' }; this.initializeEventListeners(); this.setupDragAndDrop(); } initializeEventListeners() { // File input document.getElementById('fileInput').addEventListener('change', (e) => { this.handleFileSelect(e); }); // Control sliders document.getElementById('focusPosition').addEventListener('input', (e) => { this.filters.focusPosition = e.target.value; document.getElementById('focusPositionValue').textContent = `${e.target.value}%`; this.applyTiltShift(); }); document.getElementById('blurAmount').addEventListener('input', (e) => { this.filters.blurAmount = e.target.value; document.getElementById('blurAmountValue').textContent = `${e.target.value}px`; this.applyTiltShift(); }); document.getElementById('focusSize').addEventListener('input', (e) => { this.filters.focusSize = e.target.value; document.getElementById('focusSizeValue').textContent = `${e.target.value}%`; this.applyTiltShift(); }); document.getElementById('effectType').addEventListener('change', (e) => { this.filters.effectType = e.target.value; this.applyTiltShift(); }); // Reset button document.getElementById('resetBtn').addEventListener('click', () => { this.resetFilters(); }); // Download button document.getElementById('downloadBtn').addEventListener('click', () => { this.downloadImage(); }); } setupDragAndDrop() { const dropZone = document.getElementById('dropZone'); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, this.preventDefaults, false); }); ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, () => { dropZone.classList.add('drag-over'); }, false); }); ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, () => { dropZone.classList.remove('drag-over'); }, false); }); dropZone.addEventListener('drop', (e) => { const dt = e.dataTransfer; const files = dt.files; this.handleFiles(files); }, false); } preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } handleFileSelect(e) { const files = e.target.files; this.handleFiles(files); } handleFiles(files) { if (files.length > 0) { const file = files[0]; if (file.type.match('image.*')) { const reader = new FileReader(); reader.onload = (e) => { this.loadImage(e.target.result); }; reader.readAsDataURL(file); } } } loadImage(src) { const img = new Image(); img.onload = () => { this.originalImage = img; this.currentImage = img; this.setupCanvas(); this.applyTiltShift(); document.getElementById('editorSection').classList.remove('hidden'); // Scroll to editor section document.getElementById('editorSection').scrollIntoView({ behavior: 'smooth', block: 'start' }); }; img.src = src; } setupCanvas() { const container = this.canvas.parentElement; const maxWidth = container.clientWidth - 48; // Account for padding const maxHeight = 384; // Max height from design let width = this.originalImage.width; let height = this.originalImage.height; // Maintain aspect ratio while fitting within constraints if (width > maxWidth) { height = (maxWidth / width) * height; width = maxWidth; } if (height > maxHeight) { width = (maxHeight / height) * width; height = maxHeight; } this.canvas.width = width; this.canvas.height = height; } applyTiltShift() { if (!this.originalImage) return; // Clear canvas this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // Draw original image this.ctx.drawImage(this.originalImage, 0, 0, this.canvas.width, this.canvas.height); // Apply blur effect based on tilt-shift parameters this.applyBlurEffect(); this.updateFocusIndicator(); } applyBlurEffect() { const { focusPosition, blurAmount, focusSize, effectType } = this.filters; const canvas = this.canvas; const ctx = this.ctx; // Create a temporary canvas for blur operations const tempCanvas = document.createElement('canvas'); const tempCtx = tempCanvas.getContext('2d'); tempCanvas.width = canvas.width; tempCanvas.height = canvas.height; // Copy current image to temp canvas tempCtx.drawImage(canvas, 0, 0); // Apply blur to temp canvas this.applyGaussianBlur(tempCanvas, tempCtx, blurAmount); // Calculate focus area const focusStart = (focusPosition / 100) * canvas.height - (focusSize / 200) * canvas.height; const focusEnd = (focusPosition / 100) * canvas.height + (focusSize / 200) * canvas.height; if (effectType === 'linear') { // Linear tilt-shift: sharp in the middle, blurred at top and bottom this.applyLinearGradientMask(canvas, ctx, tempCanvas, focusStart, focusEnd); } else { // Radial tilt-shift: sharp in center, blurred towards edges this.applyRadialGradientMask(canvas, ctx, tempCanvas, focusPosition); } } applyGaussianBlur(canvas, ctx, radius) { // Simple box blur approximation ctx.filter = `blur(${radius}px)`; ctx.drawImage(canvas, 0, 0); ctx.filter = 'none'; } applyLinearGradientMask(canvas, ctx, blurredCanvas, focusStart, focusEnd) { // Create gradient mask for linear tilt-shift const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); // Add color stops for the gradient gradient.addColorStop(0, 'rgba(0,0,0,0)'); gradient.addColorStop(Math.max(0, focusStart / canvas.height - 0.1), 'rgba(0,0,0,1)'); gradient.addColorStop(focusStart / canvas.height, 'rgba(0,0,0,0)'); gradient.addColorStop(focusEnd / canvas.height, 'rgba(0,0,0,0)'); gradient.addColorStop(Math.min(1, focusEnd / canvas.height + 0.1), 'rgba(0,0,0,1)'); gradient.addColorStop(1, 'rgba(0,0,0,1)'); // Draw blurred image with gradient mask ctx.save(); ctx.globalCompositeOperation = 'source-in'; ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); // Draw the sharp focus area ctx.drawImage( this.originalImage, 0, focusStart, canvas.width, focusEnd - focusStart, 0, focusStart, canvas.width, focusEnd - focusStart ); } applyRadialGradientMask(canvas, ctx, blurredCanvas, focusPosition) { // Create radial gradient mask const centerX = canvas.width / 2; const centerY = (focusPosition / 100) * canvas.height; const radius = Math.min(canvas.width, canvas.height) * 0.3; const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius); gradient.addColorStop(0, 'rgba(0,0,0,0)'); gradient.addColorStop(0.8, 'rgba(0,0,0,0)'); gradient.addColorStop(1, 'rgba(0,0,0,1)'); // Apply gradient mask ctx.save(); ctx.globalCompositeOperation = 'source-in'; ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); // Draw the sharp focus area in the center const focusSize = radius * 0.8; ctx.drawImage( this.originalImage, centerX - focusSize, centerY - focusSize, focusSize * 2, focusSize * 2, centerX - focusSize, centerY - focusSize, focusSize * 2, focusSize * 2 ); } updateFocusIndicator() { const indicator = document.getElementById('focusIndicator'); if (!this.originalImage || this.filters.effectType !== 'linear') { indicator.classList.add('hidden'); return; } const canvas = this.canvas; const focusStart = (this.filters.focusPosition / 100) * canvas.height - (this.filters.focusSize / 200) * canvas.height; const focusEnd = (this.filters.focusPosition / 100) * canvas.height + (this.filters.focusSize / 200) * canvas.height; indicator.style.top = `${focusStart}px`; indicator.style.left = '0px'; indicator.style.width = `${canvas.width}px`; indicator.style.height = `${focusEnd - focusStart}px`; indicator.classList.remove('hidden'); } resetFilters() { this.filters = { focusPosition: 50, blurAmount: 10, focusSize: 30, effectType: 'linear' }; // Update UI elements document.getElementById('focusPosition').value = 50; document.getElementById('blurAmount').value = 10; document.getElementById('focusSize').value = 30; document.getElementById('effectType').value = 'linear'; document.getElementById('focusPositionValue').textContent = '50%'; document.getElementById('blurAmountValue').textContent = '10px'; document.getElementById('focusSizeValue').textContent = '30%'; this.applyTiltShift(); } downloadImage() { if (!this.canvas) return; const link = document.createElement('a'); link.download = 'tiltshift-image.png'; link.href = this.canvas.toDataURL(); link.click(); } } // Initialize the editor when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new TiltShiftEditor(); }); // Feather icons replacement document.addEventListener('DOMContentLoaded', () => { if (typeof feather !== 'undefined') { feather.replace(); } });