YaroslavLinder commited on
Commit
ba1631b
·
verified ·
1 Parent(s): af23523

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +261 -19
index.html CHANGED
@@ -1,19 +1,261 @@
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">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Stencil Overlay Tool</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ /* Checkerboard background for transparency visualization */
10
+ .checkerboard {
11
+ background-color: #e5e5f7;
12
+ background-image: repeating-linear-gradient(45deg, #d5d5f2 25%, transparent 25%, transparent 75%, #d5d5f2 75%, #d5d5f2), repeating-linear-gradient(45deg, #d5d5f2 25%, #e5e5f7 25%, #e5e5f7 75%, #d5d5f2 75%, #d5d5f2);
13
+ background-position: 0 0, 10px 10px;
14
+ background-size: 20px 20px;
15
+ }
16
+ </style>
17
+ </head>
18
+ <body class="bg-gray-100 min-h-screen p-8 text-gray-800">
19
+
20
+ <div class="max-w-5xl mx-auto space-y-6">
21
+
22
+ <!-- Header -->
23
+ <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-200">
24
+ <h1 class="text-2xl font-bold mb-2">Stencil Overlay Composer</h1>
25
+ <p class="text-sm text-gray-600">Upload a background image and a stencil. The stencil will have its white background removed using luminance-to-alpha conversion.</p>
26
+ </div>
27
+
28
+ <!-- Controls -->
29
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
30
+
31
+ <!-- Uploads -->
32
+ <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-200 space-y-4">
33
+ <h2 class="font-semibold text-lg border-b pb-2">1. Upload Images</h2>
34
+
35
+ <div>
36
+ <label class="block text-sm font-medium text-gray-700 mb-1">Base Image (Background)</label>
37
+ <input type="file" id="baseInput" accept="image/*" class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"/>
38
+ </div>
39
+
40
+ <div>
41
+ <label class="block text-sm font-medium text-gray-700 mb-1">Stencil Image (Overlay)</label>
42
+ <input type="file" id="stencilInput" accept="image/*" class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100"/>
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Settings -->
47
+ <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-200 space-y-4">
48
+ <h2 class="font-semibold text-lg border-b pb-2">2. Adjustments</h2>
49
+
50
+ <div>
51
+ <div class="flex justify-between">
52
+ <label for="color" class="block text-sm font-medium text-gray-700">Overlay Color</label>
53
+ <span id="colorVal" class="text-sm text-gray-500">#ff0000</span>
54
+ </div>
55
+ <input type="color" id="color" value="#ff0000" class="w-full h-8 mt-1 rounded cursor-pointer">
56
+ </div>
57
+
58
+ <div>
59
+ <div class="flex justify-between">
60
+ <label for="gamma" class="block text-sm font-medium text-gray-700">Gamma (Line Thickness)</label>
61
+ <span id="gammaVal" class="text-sm text-gray-500">0.9</span>
62
+ </div>
63
+ <input type="range" id="gamma" min="0.1" max="3.0" step="0.1" value="0.9" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
64
+ <p class="text-xs text-gray-400 mt-1">&lt; 1 thickens lines, &gt; 1 thins them.</p>
65
+ </div>
66
+
67
+ <div>
68
+ <div class="flex justify-between">
69
+ <label for="opacity" class="block text-sm font-medium text-gray-700">Stencil Global Opacity</label>
70
+ <span id="opacityVal" class="text-sm text-gray-500">100%</span>
71
+ </div>
72
+ <input type="range" id="opacity" min="0" max="100" step="1" value="100" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ <!-- Preview Area -->
78
+ <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-200 flex flex-col items-center">
79
+ <h2 class="font-semibold text-lg border-b pb-4 w-full mb-4">Result</h2>
80
+
81
+ <div id="canvasContainer" class="checkerboard rounded shadow-inner overflow-hidden max-w-full">
82
+ <canvas id="mainCanvas" class="max-w-full h-auto"></canvas>
83
+ </div>
84
+
85
+ <div id="placeholder" class="py-12 text-gray-400 italic">
86
+ Upload both images to see the result
87
+ </div>
88
+
89
+ <button id="downloadBtn" class="mt-4 hidden bg-gray-800 text-white px-6 py-2 rounded-lg hover:bg-black transition-colors">
90
+ Download Result
91
+ </button>
92
+ </div>
93
+
94
+ </div>
95
+
96
+ <script>
97
+ // State
98
+ const state = {
99
+ baseImage: null,
100
+ stencilImage: null,
101
+ gamma: 0.9,
102
+ opacity: 1.0,
103
+ color: '#ff0000'
104
+ };
105
+
106
+ // DOM Elements
107
+ const baseInput = document.getElementById('baseInput');
108
+ const stencilInput = document.getElementById('stencilInput');
109
+ const colorPicker = document.getElementById('color');
110
+ const colorVal = document.getElementById('colorVal');
111
+ const gammaSlider = document.getElementById('gamma');
112
+ const gammaVal = document.getElementById('gammaVal');
113
+ const opacitySlider = document.getElementById('opacity');
114
+ const opacityVal = document.getElementById('opacityVal');
115
+ const canvas = document.getElementById('mainCanvas');
116
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
117
+ const downloadBtn = document.getElementById('downloadBtn');
118
+ const placeholder = document.getElementById('placeholder');
119
+ const canvasContainer = document.getElementById('canvasContainer');
120
+
121
+ // Event Listeners
122
+ baseInput.addEventListener('change', (e) => handleImageUpload(e, 'baseImage'));
123
+ stencilInput.addEventListener('change', (e) => handleImageUpload(e, 'stencilImage'));
124
+
125
+ colorPicker.addEventListener('input', (e) => {
126
+ state.color = e.target.value;
127
+ colorVal.textContent = state.color;
128
+ render();
129
+ });
130
+
131
+ gammaSlider.addEventListener('input', (e) => {
132
+ state.gamma = parseFloat(e.target.value);
133
+ gammaVal.textContent = state.gamma;
134
+ render();
135
+ });
136
+
137
+ opacitySlider.addEventListener('input', (e) => {
138
+ state.opacity = parseInt(e.target.value) / 100;
139
+ opacityVal.textContent = e.target.value + '%';
140
+ render();
141
+ });
142
+
143
+ downloadBtn.addEventListener('click', () => {
144
+ const link = document.createElement('a');
145
+ link.download = 'stencil-overlay.png';
146
+ link.href = canvas.toDataURL();
147
+ link.click();
148
+ });
149
+
150
+ // Image Loading Logic
151
+ function handleImageUpload(event, key) {
152
+ const file = event.target.files[0];
153
+ if (!file) return;
154
+
155
+ const reader = new FileReader();
156
+ reader.onload = (e) => {
157
+ const img = new Image();
158
+ img.onload = () => {
159
+ state[key] = img;
160
+ render();
161
+ };
162
+ img.src = e.target.result;
163
+ };
164
+ reader.readAsDataURL(file);
165
+ }
166
+
167
+ // Helper to convert Hex to RGB
168
+ function hexToRgb(hex) {
169
+ const bigint = parseInt(hex.slice(1), 16);
170
+ return {
171
+ r: (bigint >> 16) & 255,
172
+ g: (bigint >> 8) & 255,
173
+ b: bigint & 255
174
+ };
175
+ }
176
+
177
+ // Logic Ported from the provided Node.js snippet
178
+ function processStencil(img, width, height, gamma, hexColor) {
179
+ // Create a temporary canvas to read pixel data
180
+ const tempCanvas = document.createElement('canvas');
181
+ tempCanvas.width = width;
182
+ tempCanvas.height = height;
183
+ const tempCtx = tempCanvas.getContext('2d');
184
+
185
+ // Draw the raw stencil image
186
+ tempCtx.drawImage(img, 0, 0, width, height);
187
+
188
+ // Get raw pixel data
189
+ const imageData = tempCtx.getImageData(0, 0, width, height);
190
+ const data = imageData.data;
191
+
192
+ // Get target RGB values
193
+ const targetColor = hexToRgb(hexColor);
194
+
195
+ // --- CORE ALGORITHM START ---
196
+ // Adapted from sharp logic to Canvas API
197
+ for (let i = 0; i < data.length; i += 4) {
198
+ const r = data[i];
199
+ const g = data[i + 1];
200
+ const b = data[i + 2];
201
+ const a = data[i + 3];
202
+
203
+ // Calculate luminance (perceived brightness)
204
+ const luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
205
+
206
+ // Convert luminance to alpha: bright = transparent, dark = opaque
207
+ // Gamma curve allows fine-tuning
208
+ const alphaFactor = 1 - Math.pow(luminance, gamma);
209
+ const alpha = Math.round(alphaFactor * (a / 255) * 255);
210
+
211
+ // Apply the target RGB color instead of clearing it to black
212
+ data[i] = targetColor.r;
213
+ data[i + 1] = targetColor.g;
214
+ data[i + 2] = targetColor.b;
215
+ data[i + 3] = alpha;
216
+ }
217
+ // --- CORE ALGORITHM END ---
218
+
219
+ tempCtx.putImageData(imageData, 0, 0);
220
+ return tempCanvas;
221
+ }
222
+
223
+ // Main Render Loop
224
+ function render() {
225
+ if (!state.baseImage) return;
226
+
227
+ // Hide placeholder, show canvas/button
228
+ placeholder.style.display = 'none';
229
+ canvas.style.display = 'block';
230
+
231
+ // Set canvas size to match base image
232
+ canvas.width = state.baseImage.width;
233
+ canvas.height = state.baseImage.height;
234
+
235
+ // 1. Draw Base Image
236
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
237
+ ctx.drawImage(state.baseImage, 0, 0);
238
+
239
+ if (state.stencilImage) {
240
+ // 2. Process Stencil (Remove Background)
241
+ // We pass the base image dimensions to ensure the stencil fits/stretches
242
+ // exactly over the base image
243
+ const processedStencil = processStencil(
244
+ state.stencilImage,
245
+ canvas.width,
246
+ canvas.height,
247
+ state.gamma,
248
+ state.color
249
+ );
250
+
251
+ // 3. Draw Processed Stencil Overlay
252
+ ctx.globalAlpha = state.opacity;
253
+ ctx.drawImage(processedStencil, 0, 0);
254
+ ctx.globalAlpha = 1.0; // Reset
255
+
256
+ downloadBtn.classList.remove('hidden');
257
+ }
258
+ }
259
+ </script>
260
+ </body>
261
+ </html>