plugin / index (2).html
TDN-M's picture
Upload 2 files
acc759a verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OBS AI Background Remover</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]/dist/body-pix.min.js"></script>
<style>
.video-container {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
}
.video-element {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.processing-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 1.5rem;
z-index: 10;
}
.hidden {
display: none;
}
.settings-panel {
transition: all 0.3s ease;
max-height: 0;
overflow: hidden;
}
.settings-panel.open {
max-height: 500px;
}
.preview-thumbnail {
transition: all 0.2s ease;
}
.preview-thumbnail:hover {
transform: scale(1.05);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.preview-thumbnail.active {
border: 3px solid #3b82f6;
}
</style>
</head>
<body class="bg-gray-900 text-white">
<div class="container mx-auto px-4 py-8">
<div class="flex flex-col md:flex-row gap-8">
<!-- Main Content -->
<div class="flex-1">
<div class="bg-gray-800 rounded-lg shadow-xl p-6">
<h1 class="text-3xl font-bold mb-6 text-blue-400">OBS AI Background Remover</h1>
<!-- Video Preview Section -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-4">Live Preview</h2>
<div class="video-container bg-gray-700 rounded-lg overflow-hidden relative">
<video id="videoInput" class="video-element" autoplay muted></video>
<canvas id="outputCanvas" class="video-element hidden"></canvas>
<div id="processingOverlay" class="processing-overlay hidden">
<div class="text-center">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-400 mx-auto mb-4"></div>
<p>Loading AI Model...</p>
</div>
</div>
<div id="noVideoOverlay" class="processing-overlay">
<p>No video source selected</p>
</div>
</div>
</div>
<!-- Background Replacement Section -->
<div class="mb-8">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold">Background Replacement</h2>
<button id="settingsToggle" class="text-blue-400 hover:text-blue-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</button>
</div>
<!-- Settings Panel -->
<div id="settingsPanel" class="settings-panel bg-gray-700 rounded-lg p-4 mb-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-1">AI Model Quality</label>
<select id="modelQuality" class="w-full bg-gray-600 border border-gray-500 rounded-md px-3 py-2">
<option value="0.5">Fast (Low Quality)</option>
<option value="0.75" selected>Balanced</option>
<option value="1.0">High Quality (Slow)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Edge Smoothness</label>
<select id="edgeSmoothness" class="w-full bg-gray-600 border border-gray-500 rounded-md px-3 py-2">
<option value="1">Low</option>
<option value="2" selected>Medium</option>
<option value="3">High</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Background Blur</label>
<input id="bgBlur" type="range" min="0" max="10" value="0" class="w-full">
<div class="flex justify-between text-xs text-gray-400">
<span>Off</span>
<span>Max</span>
</div>
</div>
<div>
<label class="block text-sm font-medium mb-1">Foreground Brightness</label>
<input id="fgBrightness" type="range" min="80" max="120" value="100" class="w-full">
<div class="flex justify-between text-xs text-gray-400">
<span>Darker</span>
<span>Brighter</span>
</div>
</div>
</div>
</div>
<!-- Background Selection -->
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
<!-- Transparent Background -->
<div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer active" id="bgTransparent">
<div class="aspect-w-16 aspect-h-9 bg-gradient-to-br from-gray-600 to-gray-800 flex items-center justify-center">
<div class="text-center p-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span class="text-xs mt-1">Transparent</span>
</div>
</div>
</div>
<!-- Color Backgrounds -->
<div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer" id="bgBlack">
<div class="aspect-w-16 aspect-h-9 bg-black flex items-center justify-center">
<span class="text-xs">Solid Black</span>
</div>
</div>
<div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer" id="bgGray">
<div class="aspect-w-16 aspect-h-9 bg-gray-600 flex items-center justify-center">
<span class="text-xs">Solid Gray</span>
</div>
</div>
<div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer" id="bgGreen">
<div class="aspect-w-16 aspect-h-9 bg-green-600 flex items-center justify-center">
<span class="text-xs">Solid Green</span>
</div>
</div>
<!-- Custom Background Upload -->
<div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer relative" id="bgCustom">
<div class="aspect-w-16 aspect-h-9 bg-gradient-to-br from-purple-600 to-blue-600 flex items-center justify-center">
<div class="text-center p-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<span class="text-xs mt-1">Custom Image</span>
</div>
</div>
<input type="file" id="customBgInput" accept="image/*, video/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer">
</div>
<!-- Video Background Upload -->
<div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer relative" id="bgVideo">
<div class="aspect-w-16 aspect-h-9 bg-gradient-to-br from-red-600 to-yellow-600 flex items-center justify-center">
<div class="text-center p-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
<span class="text-xs mt-1">Custom Video</span>
</div>
</div>
<input type="file" id="customVideoInput" accept="video/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer">
</div>
<!-- Blur Background -->
<div class="preview-thumbnail bg-gray-700 rounded-lg overflow-hidden cursor-pointer" id="bgBlurred">
<div class="aspect-w-16 aspect-h-9 bg-gray-600 flex items-center justify-center relative overflow-hidden">
<div class="absolute inset-0 bg-gray-400 filter blur-md"></div>
<span class="text-xs relative z-10">Blurred</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Controls Sidebar -->
<div class="w-full md:w-80">
<div class="bg-gray-800 rounded-lg shadow-xl p-6 sticky top-6">
<h2 class="text-xl font-semibold mb-4">Controls</h2>
<!-- Video Source Selection -->
<div class="mb-6">
<label class="block text-sm font-medium mb-2">Video Source</label>
<select id="videoSource" class="w-full bg-gray-600 border border-gray-500 rounded-md px-3 py-2">
<option value="">Select a video source</option>
</select>
</div>
<!-- Toggle Buttons -->
<div class="space-y-4 mb-6">
<div>
<label class="inline-flex items-center">
<input type="checkbox" id="toggleEffect" class="form-checkbox h-5 w-5 text-blue-400">
<span class="ml-2">Enable Background Removal</span>
</label>
</div>
<div>
<label class="inline-flex items-center">
<input type="checkbox" id="togglePreview" class="form-checkbox h-5 w-5 text-blue-400" checked>
<span class="ml-2">Show Preview</span>
</label>
</div>
</div>
<!-- Performance Stats -->
<div class="bg-gray-700 rounded-lg p-4 mb-6">
<h3 class="text-sm font-medium mb-2 text-gray-400">Performance</h3>
<div class="grid grid-cols-2 gap-2 text-sm">
<div>
<div class="text-gray-400">Processing Time:</div>
<div id="processingTime">0 ms</div>
</div>
<div>
<div class="text-gray-400">FPS:</div>
<div id="fpsCounter">0</div>
</div>
</div>
</div>
<!-- OBS Integration -->
<div class="bg-blue-900 bg-opacity-20 rounded-lg p-4 border border-blue-800">
<h3 class="text-sm font-medium mb-2 text-blue-400">OBS Integration</h3>
<p class="text-xs text-gray-400 mb-3">Add this as a Browser Source in OBS with these settings:</p>
<div class="text-xs space-y-1">
<div class="flex justify-between">
<span class="text-gray-400">Width:</span>
<span>1920</span>
</div>
<div class="flex justify-between">
<span class="text-gray-400">Height:</span>
<span>1080</span>
</div>
<div class="flex justify-between">
<span class="text-gray-400">Custom CSS:</span>
<span>None</span>
</div>
</div>
<button id="copyOBSLink" class="mt-3 w-full bg-blue-600 hover:bg-blue-500 text-white py-2 px-4 rounded-md text-sm flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
</svg>
Copy OBS Browser Source URL
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// DOM Elements
const videoInput = document.getElementById('videoInput');
const outputCanvas = document.getElementById('outputCanvas');
const processingOverlay = document.getElementById('processingOverlay');
const noVideoOverlay = document.getElementById('noVideoOverlay');
const videoSourceSelect = document.getElementById('videoSource');
const toggleEffect = document.getElementById('toggleEffect');
const togglePreview = document.getElementById('togglePreview');
const modelQuality = document.getElementById('modelQuality');
const edgeSmoothness = document.getElementById('edgeSmoothness');
const bgBlur = document.getElementById('bgBlur');
const fgBrightness = document.getElementById('fgBrightness');
const settingsToggle = document.getElementById('settingsToggle');
const settingsPanel = document.getElementById('settingsPanel');
const processingTimeElement = document.getElementById('processingTime');
const fpsCounterElement = document.getElementById('fpsCounter');
const copyOBSLink = document.getElementById('copyOBSLink');
// Background selection elements
const bgThumbnails = document.querySelectorAll('.preview-thumbnail');
const bgTransparent = document.getElementById('bgTransparent');
const bgBlack = document.getElementById('bgBlack');
const bgGray = document.getElementById('bgGray');
const bgGreen = document.getElementById('bgGreen');
const bgCustom = document.getElementById('bgCustom');
const bgVideo = document.getElementById('bgVideo');
const bgBlurred = document.getElementById('bgBlurred');
const customBgInput = document.getElementById('customBgInput');
const customVideoInput = document.getElementById('customVideoInput');
// Variables
let net;
let isProcessing = false;
let lastTimestamp = 0;
let frameCount = 0;
let fps = 0;
let currentBackground = 'transparent';
let customBackgroundImage = null;
let customBackgroundVideo = null;
let backgroundVideoElement = null;
// Initialize
document.addEventListener('DOMContentLoaded', async () => {
// Load video sources
loadVideoSources();
// Initialize settings panel toggle
settingsToggle.addEventListener('click', () => {
settingsPanel.classList.toggle('open');
});
// Initialize background selection
bgThumbnails.forEach(thumb => {
thumb.addEventListener('click', () => {
bgThumbnails.forEach(t => t.classList.remove('active'));
thumb.classList.add('active');
if (thumb === bgTransparent) {
currentBackground = 'transparent';
} else if (thumb === bgBlack) {
currentBackground = 'black';
} else if (thumb === bgGray) {
currentBackground = 'gray';
} else if (thumb === bgGreen) {
currentBackground = 'green';
} else if (thumb === bgCustom) {
currentBackground = 'customImage';
customBgInput.click();
} else if (thumb === bgVideo) {
currentBackground = 'customVideo';
customVideoInput.click();
} else if (thumb === bgBlurred) {
currentBackground = 'blurred';
}
});
});
// Handle custom background upload
customBgInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
customBackgroundImage = new Image();
customBackgroundImage.src = event.target.result;
customBackgroundImage.onload = () => {
bgCustom.classList.add('active');
};
};
reader.readAsDataURL(file);
}
});
// Handle custom video background upload
customVideoInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
if (backgroundVideoElement) {
backgroundVideoElement.pause();
backgroundVideoElement.remove();
}
backgroundVideoElement = document.createElement('video');
backgroundVideoElement.autoplay = true;
backgroundVideoElement.loop = true;
backgroundVideoElement.muted = true;
backgroundVideoElement.src = URL.createObjectURL(file);
backgroundVideoElement.style.display = 'none';
document.body.appendChild(backgroundVideoElement);
bgVideo.classList.add('active');
}
});
// Toggle effect
toggleEffect.addEventListener('change', async () => {
if (toggleEffect.checked) {
await initBodyPix();
} else {
stopProcessing();
}
});
// Toggle preview
togglePreview.addEventListener('change', () => {
if (togglePreview.checked) {
if (toggleEffect.checked) {
outputCanvas.classList.remove('hidden');
videoInput.classList.add('hidden');
} else {
videoInput.classList.remove('hidden');
}
} else {
outputCanvas.classList.add('hidden');
videoInput.classList.add('hidden');
}
});
// Video source selection
videoSourceSelect.addEventListener('change', () => {
const deviceId = videoSourceSelect.value;
if (deviceId) {
startVideo(deviceId);
} else {
stopVideo();
}
});
// Copy OBS link
copyOBSLink.addEventListener('click', () => {
const currentUrl = window.location.href;
navigator.clipboard.writeText(currentUrl).then(() => {
const originalText = copyOBSLink.textContent;
copyOBSLink.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
Copied!
`;
setTimeout(() => {
copyOBSLink.innerHTML = originalText;
}, 2000);
});
});
// Start FPS counter
setInterval(updateFPS, 1000);
});
// Load available video sources
async function loadVideoSources() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === 'videoinput');
videoDevices.forEach(device => {
const option = document.createElement('option');
option.value = device.deviceId;
option.text = device.label || `Camera ${videoSourceSelect.length + 1}`;
videoSourceSelect.appendChild(option);
});
// Select the first camera by default if available
if (videoDevices.length > 0) {
videoSourceSelect.value = videoDevices[0].deviceId;
startVideo(videoDevices[0].deviceId);
}
} catch (err) {
console.error('Error enumerating devices:', err);
}
}
// Start video stream
async function startVideo(deviceId) {
try {
const constraints = {
video: {
deviceId: { exact: deviceId },
width: { ideal: 1280 },
height: { ideal: 720 }
}
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
videoInput.srcObject = stream;
noVideoOverlay.classList.add('hidden');
// Show video input by default
if (!toggleEffect.checked) {
videoInput.classList.remove('hidden');
outputCanvas.classList.add('hidden');
}
// Start processing if effect is enabled
if (toggleEffect.checked) {
await initBodyPix();
videoInput.classList.add('hidden');
outputCanvas.classList.remove('hidden');
}
} catch (err) {
console.error('Error starting video:', err);
noVideoOverlay.textContent = 'Error accessing camera';
}
}
// Stop video stream
function stopVideo() {
if (videoInput.srcObject) {
videoInput.srcObject.getTracks().forEach(track => track.stop());
videoInput.srcObject = null;
}
videoInput.classList.add('hidden');
outputCanvas.classList.add('hidden');
noVideoOverlay.classList.remove('hidden');
stopProcessing();
}
// Initialize BodyPix model
async function initBodyPix() {
if (net) return;
processingOverlay.classList.remove('hidden');
outputCanvas.classList.remove('hidden');
try {
// Load the BodyPix model with selected quality
const multiplier = parseFloat(modelQuality.value);
net = await bodyPix.load({
architecture: 'MobileNetV1',
outputStride: 16,
multiplier: multiplier,
quantBytes: 2
});
processingOverlay.classList.add('hidden');
startProcessing();
} catch (err) {
console.error('Error loading BodyPix model:', err);
processingOverlay.textContent = 'Error loading AI model';
toggleEffect.checked = false;
}
}
// Start background removal processing
function startProcessing() {
if (!net || isProcessing) return;
isProcessing = true;
outputCanvas.width = videoInput.videoWidth;
outputCanvas.height = videoInput.videoHeight;
processFrame();
}
// Stop background removal processing
function stopProcessing() {
isProcessing = false;
if (net) {
// net.dispose(); // Don't dispose if we might reuse it
net = null;
}
}
// Process each video frame
async function processFrame() {
if (!isProcessing || !net || videoInput.readyState < 2) {
if (isProcessing) {
requestAnimationFrame(processFrame);
}
return;
}
const startTime = performance.now();
try {
// Perform segmentation
const segmentation = await net.segmentPerson(videoInput, {
flipHorizontal: false,
internalResolution: 'medium',
segmentationThreshold: 0.7
});
// Get canvas context
const ctx = outputCanvas.getContext('2d');
// Draw the background based on selection
ctx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
if (currentBackground === 'transparent') {
// For transparent, we just leave it clear
} else if (currentBackground === 'black') {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
} else if (currentBackground === 'gray') {
ctx.fillStyle = '#4B5563';
ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
} else if (currentBackground === 'green') {
ctx.fillStyle = '#059669';
ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
} else if (currentBackground === 'customImage' && customBackgroundImage) {
// Calculate aspect ratio and draw custom background
const imgAspect = customBackgroundImage.width / customBackgroundImage.height;
const canvasAspect = outputCanvas.width / outputCanvas.height;
if (imgAspect > canvasAspect) {
// Image is wider than canvas
const scale = outputCanvas.width / customBackgroundImage.width;
const scaledHeight = customBackgroundImage.height * scale;
const y = (outputCanvas.height - scaledHeight) / 2;
ctx.drawImage(customBackgroundImage, 0, y, outputCanvas.width, scaledHeight);
} else {
// Image is taller than canvas
const scale = outputCanvas.height / customBackgroundImage.height;
const scaledWidth = customBackgroundImage.width * scale;
const x = (outputCanvas.width - scaledWidth) / 2;
ctx.drawImage(customBackgroundImage, x, 0, scaledWidth, outputCanvas.height);
}
} else if (currentBackground === 'customVideo' && backgroundVideoElement) {
// Draw video background
const videoAspect = backgroundVideoElement.videoWidth / backgroundVideoElement.videoHeight;
const canvasAspect = outputCanvas.width / outputCanvas.height;
if (videoAspect > canvasAspect) {
// Video is wider than canvas
const scale = outputCanvas.width / backgroundVideoElement.videoWidth;
const scaledHeight = backgroundVideoElement.videoHeight * scale;
const y = (outputCanvas.height - scaledHeight) / 2;
ctx.drawImage(backgroundVideoElement, 0, y, outputCanvas.width, scaledHeight);
} else {
// Video is taller than canvas
const scale = outputCanvas.height / backgroundVideoElement.videoHeight;
const scaledWidth = backgroundVideoElement.videoWidth * scale;
const x = (outputCanvas.width - scaledWidth) / 2;
ctx.drawImage(backgroundVideoElement, x, 0, scaledWidth, outputCanvas.height);
}
} else if (currentBackground === 'blurred') {
// Draw blurred background
ctx.drawImage(videoInput, 0, 0, outputCanvas.width, outputCanvas.height);
applyBlurEffect(ctx, outputCanvas.width, outputCanvas.height, parseInt(bgBlur.value));
}
// Draw the foreground (person) with alpha mask
const foregroundColor = { r: 255, g: 255, b: 255, a: 255 };
const backgroundColor = { r: 0, g: 0, b: 0, a: 0 };
const edgeBlurAmount = parseInt(edgeSmoothness.value);
const brightness = parseInt(fgBrightness.value) / 100;
bodyPix.drawBokehEffect(
outputCanvas,
videoInput,
segmentation,
parseFloat(bgBlur.value),
foregroundColor,
backgroundColor,
edgeBlurAmount,
brightness
);
// Update performance stats
const endTime = performance.now();
const processingTime = endTime - startTime;
processingTimeElement.textContent = `${processingTime.toFixed(1)} ms`;
frameCount++;
// Process next frame
requestAnimationFrame(processFrame);
} catch (err) {
console.error('Error processing frame:', err);
isProcessing = false;
}
}
// Apply blur effect to context
function applyBlurEffect(ctx, width, height, radius) {
if (radius <= 0) return;
// Create temporary canvas for blur effect
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d');
// Draw the current content to temp canvas
tempCtx.drawImage(outputCanvas, 0, 0, width, height);
// Apply blur by drawing scaled down and up
const iterations = Math.min(radius, 10);
const scale = 1 / (iterations * 0.5 + 1);
for (let i = 0; i < iterations; i++) {
// Scale down
const scaledWidth = width * scale;
const scaledHeight = height * scale;
tempCtx.drawImage(
tempCanvas,
0, 0, width, height,
0, 0, scaledWidth, scaledHeight
);
// Scale up
tempCtx.drawImage(
tempCanvas,
0, 0, scaledWidth, scaledHeight,
0, 0, width, height
);
}
// Draw the blurred content back to main canvas
ctx.drawImage(tempCanvas, 0, 0, width, height);
}
// Update FPS counter
function updateFPS() {
const now = performance.now();
if (lastTimestamp) {
const delta = (now - lastTimestamp) / 1000;
fps = Math.round(frameCount / delta);
fpsCounterElement.textContent = fps;
}
lastTimestamp = now;
frameCount = 0;
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=TDN-M/plugin" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>