tiltshift-studio / script.js
HealeyV3's picture
Create a single page website that allows user to drag and drop a single image. Apply a tilt shift to the image, allowing the user to control the basic tilt shift variables.
ccbd256 verified
Raw
History Blame Contribute Delete
11.1 kB
// 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();
}
});