You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, frameIntervalSec = "1", extractFormat = "jpeg", quality = "0.9") {
// UI container for the Movie to Image converter
const container = document.createElement('div');
container.style.fontFamily = 'system-ui, -apple-system, sans-serif';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.alignItems = 'center';
container.style.width = '100%';
container.style.boxSizing = 'border-box';
container.style.padding = '20px';
const video = document.createElement('video');
video.style.maxWidth = '100%';
video.style.maxHeight = '500px';
video.style.borderRadius = '8px';
video.style.backgroundColor = '#000';
video.controls = true;
video.crossOrigin = 'anonymous';
// By setting video.src to originalImg.src, we handle situations where
// a video file's blob/data URL was routed to an Image object. It handles
// video playback perfectly if the dragged file was a "movie".
video.src = originalImg.src;
const controlsContainer = document.createElement('div');
controlsContainer.style.display = 'flex';
controlsContainer.style.gap = '15px';
controlsContainer.style.marginTop = '20px';
controlsContainer.style.flexWrap = 'wrap';
controlsContainer.style.justifyContent = 'center';
const captureCurrentBtn = document.createElement('button');
captureCurrentBtn.textContent = 'Capture Current Frame';
captureCurrentBtn.style.padding = '10px 20px';
captureCurrentBtn.style.border = 'none';
captureCurrentBtn.style.borderRadius = '5px';
captureCurrentBtn.style.backgroundColor = '#4CAF50';
captureCurrentBtn.style.color = 'white';
captureCurrentBtn.style.cursor = 'pointer';
captureCurrentBtn.style.fontSize = '14px';
const captureIntervalBtn = document.createElement('button');
captureIntervalBtn.textContent = `Extract Frame Every ${frameIntervalSec}s`;
captureIntervalBtn.style.padding = '10px 20px';
captureIntervalBtn.style.border = 'none';
captureIntervalBtn.style.borderRadius = '5px';
captureIntervalBtn.style.backgroundColor = '#2196F3';
captureIntervalBtn.style.color = 'white';
captureIntervalBtn.style.cursor = 'pointer';
captureIntervalBtn.style.fontSize = '14px';
controlsContainer.appendChild(captureCurrentBtn);
controlsContainer.appendChild(captureIntervalBtn);
const helpText = document.createElement('p');
helpText.textContent = 'Extracted frames will appear below. Click any frame to download it.';
helpText.style.fontSize = '14px';
helpText.style.color = '#666';
helpText.style.marginTop = '15px';
const gallery = document.createElement('div');
gallery.style.display = 'flex';
gallery.style.flexWrap = 'wrap';
gallery.style.gap = '15px';
gallery.style.marginTop = '20px';
gallery.style.width = '100%';
gallery.style.justifyContent = 'center';
container.appendChild(video);
container.appendChild(controlsContainer);
container.appendChild(helpText);
container.appendChild(gallery);
function createThumb(canvas, time) {
const wrapper = document.createElement('div');
wrapper.style.display = 'flex';
wrapper.style.flexDirection = 'column';
wrapper.style.alignItems = 'center';
const q = parseFloat(quality) || 0.9;
const fmt = ['png', 'webp'].includes(extractFormat.toLowerCase()) ? extractFormat.toLowerCase() : 'jpeg';
const img = document.createElement('img');
try {
img.src = canvas.toDataURL(`image/${fmt}`, q);
} catch(e) {
alert("Security error: Cannot extract frame from cross-origin video.");
return;
}
img.style.height = '150px';
img.style.borderRadius = '4px';
img.style.border = '1px solid #ddd';
img.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
img.style.cursor = 'pointer';
img.title = 'Click to download frame';
img.style.transition = 'transform 0.2s';
img.onmouseover = () => img.style.transform = 'scale(1.05)';
img.onmouseout = () => img.style.transform = 'scale(1)';
const label = document.createElement('span');
label.textContent = `${time.toFixed(2)}s`;
label.style.fontSize = '13px';
label.style.marginTop = '6px';
label.style.color = '#333';
label.style.fontWeight = '500';
wrapper.appendChild(img);
wrapper.appendChild(label);
gallery.appendChild(wrapper);
img.onclick = () => {
const a = document.createElement('a');
a.href = img.src;
a.download = `frame_${time.toFixed(2)}.${fmt}`;
a.click();
};
}
function captureFrame() {
if (!video.videoWidth || !video.videoHeight) {
alert("Video dimensions not available yet. Please wait for the video to load fully.");
return;
}
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
createThumb(canvas, video.currentTime);
}
captureCurrentBtn.onclick = () => {
captureFrame();
};
captureIntervalBtn.onclick = () => {
const duration = video.duration;
if (!duration || isNaN(duration) || duration === Infinity) {
alert("Video duration not available. Please wait for video to buffer or click play once.");
return;
}
const interval = parseFloat(frameIntervalSec) || 1;
if (interval <= 0) return;
captureIntervalBtn.disabled = true;
captureCurrentBtn.disabled = true;
const prevText = captureIntervalBtn.textContent;
captureIntervalBtn.textContent = 'Extracting...';
const originalTime = video.currentTime;
const wasPlaying = !video.paused;
video.pause();
let currentTime = 0;
function onSeeked() {
captureFrame();
currentTime += interval;
if (currentTime <= duration) {
video.currentTime = currentTime;
} else {
video.removeEventListener('seeked', onSeeked);
video.currentTime = originalTime;
captureIntervalBtn.textContent = prevText;
captureIntervalBtn.disabled = false;
captureCurrentBtn.disabled = false;
if (wasPlaying) video.play();
}
}
video.addEventListener('seeked', onSeeked);
video.currentTime = currentTime;
};
// Fallback: If originalImg.src is truly an image (not a video), video loading will trigger an error.
// In this case, we act as a "Movie to Image Converter" by applying a cinematic movie frame effect gracefully.
video.onerror = () => {
video.style.display = 'none';
controlsContainer.style.display = 'none';
helpText.style.display = 'none';
gallery.style.display = 'none';
const canvas = document.createElement('canvas');
// Wait for image dimensions if not already loaded fully
const imgW = originalImg.naturalWidth || originalImg.width || 800;
const imgH = originalImg.naturalHeight || originalImg.height || 600;
// Target an anamorphic 2.35:1 aspect ratio typical in mainstream movies
const targetAspectRatio = 2.35;
canvas.width = imgW;
canvas.height = imgW / targetAspectRatio;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const sHeight = imgW / targetAspectRatio;
const sY = (imgH - sHeight) / 2;
if (sHeight <= imgH) {
// Crop source top and bottom to create widescreen centered shot
ctx.drawImage(originalImg, 0, sY, imgW, sHeight, 0, 0, canvas.width, canvas.height);
} else {
// Add classic letterbox bars if the original image is wider than 2.35:1 natively
const dHeight = imgW / (imgW / imgH);
const dY = (canvas.height - dHeight) / 2;
ctx.drawImage(originalImg, 0, 0, imgW, imgH, 0, dY, canvas.width, dHeight);
}
// Add a Cinematic Color Grade: Teal & Orange blend
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = 'rgba(255, 150, 50, 0.15)'; // Warm highlight filter
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'color';
ctx.fillStyle = 'rgba(0, 100, 150, 0.10)'; // Cool shadow cast filter
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'source-over';
// Add subtle film grain
try {
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imgData.data;
for (let i = 0; i < pixels.length; i += 4) {
const noise = (Math.random() - 0.5) * 15;
pixels[i] += noise;
pixels[i+1] += noise;
pixels[i+2] += noise;
}
ctx.putImageData(imgData, 0, 0);
} catch (e) {
console.warn("Skipping film grain due to Cross-Origin restrictions on original image.");
}
// Subtitles (Movie aesthetic touch)
ctx.font = `italic ${Math.max(16, canvas.height * 0.05)}px sans-serif`;
ctx.textAlign = 'center';
ctx.fillStyle = '#FFDD00'; // Standard vintage yellow subtitles
ctx.strokeStyle = '#000';
ctx.lineWidth = Math.max(2, canvas.height * 0.005);
const subtitleText = "Cinematic Mode Enabled";
ctx.strokeText(subtitleText, canvas.width / 2, canvas.height * 0.9);
ctx.fillText(subtitleText, canvas.width / 2, canvas.height * 0.9);
canvas.style.maxWidth = '100%';
canvas.style.borderRadius = '8px';
canvas.style.boxShadow = '0 4px 15px rgba(0,0,0,0.3)';
container.appendChild(canvas);
const fbText = document.createElement('p');
fbText.textContent = 'Input was an image, not a playable movie file. Applied Cinematic Filter instead.';
fbText.style.color = '#777';
fbText.style.fontSize = '14px';
fbText.style.marginTop = '15px';
container.appendChild(fbText);
};
return container;
}
Apply Changes