Natkat1 commited on
Commit
e92cdc4
·
verified ·
1 Parent(s): 47469b0

Generate a single-file HTML application that performs background removal entirely in the browser.

Browse files

Requirements:

Engine: Use the @imgly/background-removal library, which provides a WebAssembly (WASM) wrapper around ONNX models.

Client-Side Processing: The app must process images 100% locally. After the initial download of model assets (approximately 20–40 MB, cached afterward), all computation should run on the user's CPU/GPU. No server uploads.

User Interface: Include a modern, responsive UI with drag-and-drop support and a transparency grid for displaying the output.

First-Use Notice: Inform users that the first image may take 10–30 seconds as the WASM engine and models load, while subsequent images should process in 1–2 seconds.

Files changed (4) hide show
  1. README.md +8 -5
  2. index.html +104 -19
  3. script.js +163 -0
  4. style.css +14 -24
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Magic Background Remover
3
- emoji: 🏃
4
- colorFrom: yellow
5
- colorTo: blue
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: Magic Background Remover 🔮
3
+ colorFrom: purple
4
+ colorTo: purple
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
index.html CHANGED
@@ -1,19 +1,104 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Magic Background Remover 🔮</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://unpkg.com/feather-icons"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/@imgly/background-removal"></script>
12
+ <script>
13
+ tailwind.config = {
14
+ darkMode: 'class',
15
+ theme: {
16
+ extend: {
17
+ colors: {
18
+ primary: {
19
+ 500: '#f97316',
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }
25
+ </script>
26
+ </head>
27
+ <body class="bg-gray-900 text-gray-100 min-h-screen">
28
+ <div class="container mx-auto px-4 py-8">
29
+ <header class="text-center mb-10">
30
+ <h1 class="text-4xl font-bold text-primary-500 mb-2">Magic Background Remover <span class="text-3xl">🔮</span></h1>
31
+ <p class="text-gray-400">Remove backgrounds from images instantly in your browser</p>
32
+ </header>
33
+
34
+ <main>
35
+ <div class="max-w-4xl mx-auto bg-gray-800 rounded-xl p-6 shadow-lg">
36
+ <div id="drop-area" class="border-2 border-dashed border-primary-500 rounded-xl p-12 text-center cursor-pointer hover:bg-gray-700 transition-colors duration-200">
37
+ <div id="upload-ui">
38
+ <i data-feather="upload" class="w-12 h-12 mx-auto text-primary-500 mb-4"></i>
39
+ <h2 class="text-xl font-semibold mb-2">Drag & Drop your image here</h2>
40
+ <p class="text-gray-400 mb-4">or click to browse files</p>
41
+ <input type="file" id="file-input" accept="image/*" class="hidden">
42
+ <button id="browse-btn" class="bg-primary-500 hover:bg-primary-600 text-white font-medium py-2 px-6 rounded-lg transition-colors duration-200">
43
+ Select Image
44
+ </button>
45
+ </div>
46
+ <div id="processing-ui" class="hidden">
47
+ <div class="flex flex-col items-center">
48
+ <i data-feather="loader" class="w-12 h-12 mx-auto text-primary-500 mb-4 animate-spin"></i>
49
+ <h2 class="text-xl font-semibold mb-2">Processing image...</h2>
50
+ <p class="text-gray-400" id="progress-text">Loading models for the first time...</p>
51
+ <div class="w-full bg-gray-700 rounded-full h-2.5 mt-4">
52
+ <div id="progress-bar" class="bg-primary-500 h-2.5 rounded-full" style="width: 0%"></div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-8">
59
+ <div>
60
+ <h3 class="font-medium text-gray-300 mb-2">Original Image</h3>
61
+ <div class="bg-gray-700 rounded-lg overflow-hidden flex items-center justify-center h-64">
62
+ <img id="original-image" class="max-w-full max-h-full hidden">
63
+ </div>
64
+ </div>
65
+ <div>
66
+ <h3 class="font-medium text-gray-300 mb-2">Result</h3>
67
+ <div class="bg-gray-700 rounded-lg overflow-hidden flex items-center justify-center h-64 bg-checkerboard">
68
+ <img id="result-image" class="max-w-full max-h-full hidden">
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <div id="download-section" class="mt-6 hidden">
74
+ <button id="download-btn" class="bg-primary-500 hover:bg-primary-600 text-white font-medium py-2 px-6 rounded-lg transition-colors duration-200 flex items-center gap-2">
75
+ <i data-feather="download"></i>
76
+ Download Result
77
+ </button>
78
+ </div>
79
+ </div>
80
+
81
+ <div id="first-run-notice" class="max-w-2xl mx-auto mt-8 p-4 bg-gray-800 border-l-4 border-primary-500 rounded-r">
82
+ <div class="flex items-start">
83
+ <i data-feather="alert-circle" class="w-5 h-5 text-primary-500 mr-2 mt-0.5"></i>
84
+ <div>
85
+ <h3 class="font-medium">First Run Notice</h3>
86
+ <p class="text-gray-400 text-sm mt-1">The first image may take 10-30 seconds as the engine loads (approximately 20-40 MB). Subsequent images will process in 1-2 seconds.</p>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </main>
91
+
92
+ <footer class="mt-16 text-center text-gray-500 text-sm">
93
+ <p>All processing happens 100% in your browser. No images are uploaded to any server.</p>
94
+ <p class="mt-1">Powered by <span class="text-primary-500">@imgly/background-removal</span> and ONNX</p>
95
+ </footer>
96
+ </div>
97
+
98
+ <script src="script.js"></script>
99
+ <script>
100
+ feather.replace();
101
+ </script>
102
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
103
+ </body>
104
+ </html>
script.js ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const dropArea = document.getElementById('drop-area');
3
+ const fileInput = document.getElementById('file-input');
4
+ const browseBtn = document.getElementById('browse-btn');
5
+ const uploadUI = document.getElementById('upload-ui');
6
+ const processingUI = document.getElementById('processing-ui');
7
+ const originalImage = document.getElementById('original-image');
8
+ const resultImage = document.getElementById('result-image');
9
+ const downloadBtn = document.getElementById('download-btn');
10
+ const downloadSection = document.getElementById('download-section');
11
+ const progressText = document.getElementById('progress-text');
12
+ const progressBar = document.getElementById('progress-bar');
13
+
14
+ let isFirstRun = true;
15
+ let processing = false;
16
+ let currentFile = null;
17
+
18
+ // Prevent default drag behaviors
19
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
20
+ dropArea.addEventListener(eventName, preventDefaults, false);
21
+ });
22
+
23
+ function preventDefaults(e) {
24
+ e.preventDefault();
25
+ e.stopPropagation();
26
+ }
27
+
28
+ // Highlight drop area when item is dragged over it
29
+ ['dragenter', 'dragover'].forEach(eventName => {
30
+ dropArea.addEventListener(eventName, highlight, false);
31
+ });
32
+
33
+ ['dragleave', 'drop'].forEach(eventName => {
34
+ dropArea.addEventListener(eventName, unhighlight, false);
35
+ });
36
+
37
+ function highlight() {
38
+ dropArea.classList.add('highlight');
39
+ }
40
+
41
+ function unhighlight() {
42
+ dropArea.classList.remove('highlight');
43
+ }
44
+
45
+ // Handle dropped files
46
+ dropArea.addEventListener('drop', handleDrop, false);
47
+
48
+ function handleDrop(e) {
49
+ const dt = e.dataTransfer;
50
+ const files = dt.files;
51
+ if (files.length && !processing) {
52
+ handleFiles(files);
53
+ }
54
+ }
55
+
56
+ // Handle file input
57
+ fileInput.addEventListener('change', function() {
58
+ if (this.files.length && !processing) {
59
+ handleFiles(this.files);
60
+ }
61
+ });
62
+
63
+ // Browse button click
64
+ browseBtn.addEventListener('click', () => {
65
+ fileInput.click();
66
+ });
67
+
68
+ // Handle file processing
69
+ async function handleFiles(files) {
70
+ const file = files[0];
71
+ if (!file.type.match('image.*')) {
72
+ alert('Please select an image file');
73
+ return;
74
+ }
75
+
76
+ currentFile = file;
77
+ processing = true;
78
+
79
+ // Show original image
80
+ const reader = new FileReader();
81
+ reader.onload = function(e) {
82
+ originalImage.src = e.target.result;
83
+ originalImage.classList.remove('hidden');
84
+ };
85
+ reader.readAsDataURL(file);
86
+
87
+ // Show processing UI
88
+ uploadUI.classList.add('hidden');
89
+ processingUI.classList.remove('hidden');
90
+
91
+ if (isFirstRun) {
92
+ progressText.textContent = 'Loading models for the first time (20-40MB)...';
93
+ progressBar.style.width = '10%';
94
+ } else {
95
+ progressText.textContent = 'Processing image...';
96
+ progressBar.style.width = '30%';
97
+ }
98
+
99
+ try {
100
+ // Initialize the background removal
101
+ const blobURL = URL.createObjectURL(file);
102
+
103
+ const progressCallback = (progress) => {
104
+ const percent = Math.floor(progress * 100);
105
+ progressBar.style.width = `${percent}%`;
106
+
107
+ if (isFirstRun) {
108
+ if (progress < 0.9) {
109
+ progressText.textContent = `Downloading models... ${percent}%`;
110
+ } else {
111
+ progressText.textContent = 'Processing image...';
112
+ }
113
+ } else {
114
+ progressText.textContent = `Processing... ${percent}%`;
115
+ }
116
+ };
117
+
118
+ const result = await backgroundRemoval(blobURL, {
119
+ progress: progressCallback,
120
+ debug: true
121
+ });
122
+
123
+ // Convert Blob to URL for display
124
+ const resultURL = URL.createObjectURL(result);
125
+
126
+ // Display result
127
+ resultImage.src = resultURL;
128
+ resultImage.classList.remove('hidden');
129
+
130
+ // Enable download button
131
+ downloadBtn.onclick = function() {
132
+ const a = document.createElement('a');
133
+ a.href = resultURL;
134
+ a.download = `background-removed-${file.name}`;
135
+ document.body.appendChild(a);
136
+ a.click();
137
+ document.body.removeChild(a);
138
+ setTimeout(() => URL.revokeObjectURL(resultURL), 100);
139
+ };
140
+
141
+ downloadSection.classList.remove('hidden');
142
+
143
+ // Reset UI
144
+ processing = false;
145
+ uploadUI.classList.remove('hidden');
146
+ processingUI.classList.add('hidden');
147
+
148
+ if (isFirstRun) {
149
+ isFirstRun = false;
150
+ document.getElementById('first-run-notice').classList.add('hidden');
151
+ }
152
+
153
+ } catch (error) {
154
+ console.error('Error during background removal:', error);
155
+ alert('An error occurred during background removal. Please try again.');
156
+
157
+ // Reset UI
158
+ processing = false;
159
+ uploadUI.classList.remove('hidden');
160
+ processingUI.classList.add('hidden');
161
+ }
162
+ }
163
+ });
style.css CHANGED
@@ -1,28 +1,18 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }
 
1
+ .bg-checkerboard {
2
+ background-image:
3
+ linear-gradient(45deg, #555 25%, transparent 25%),
4
+ linear-gradient(-45deg, #555 25%, transparent 25%),
5
+ linear-gradient(45deg, transparent 75%, #555 75%),
6
+ linear-gradient(-45deg, transparent 75%, #555 75%);
7
+ background-size: 20px 20px;
8
+ background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
9
  }
10
 
11
+ #drop-area.highlight {
12
+ background-color: rgba(249, 115, 22, 0.1);
13
+ border-color: rgba(249, 115, 22, 0.8);
14
  }
15
 
16
+ #original-image, #result-image {
17
+ object-fit: contain;
18
+ }