MaartenGr commited on
Commit
69e68c1
·
verified ·
1 Parent(s): a1d8103

Upload 4 files

Browse files
Files changed (5) hide show
  1. .gitattributes +2 -0
  2. README.md +47 -7
  3. index.html +765 -19
  4. sample_12b.png +3 -0
  5. sample_paper.png +3 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ sample_12b.png filter=lfs diff=lfs merge=lfs -text
37
+ sample_paper.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,52 @@
1
  ---
2
- title: Gemma4 Vision Token Budget
3
- emoji: 🔥
4
- colorFrom: green
5
- colorTo: green
6
  sdk: static
7
  pinned: false
8
- license: apache-2.0
9
- short_description: Aspect-Ratio Preserving Image Resizing for Gemma 4
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Gemma 4 - Vision Token Budget
3
+ emoji: 🖼️
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
+ # Gemma 4 - Vision Token Budget
11
+ **Aspect-Ratio Preserving Image Resizing**
12
+
13
+ An interactive, client-side web visualizer demonstrating **Gemma 4's Aspect-Ratio Preserving Image Resizing and Token Discretization**. Explore how different visual budgets ($N_{\max} \in \{70, 140, 280, 560, 1120\}$) impact resolution, token consumption, and visual detail in real-time right inside your browser.
14
+
15
+ ---
16
+
17
+ ## Overview
18
+
19
+ Gemma 4 introduces fine-grained, dynamic control over its multimodal visual processing budget. Unlike traditional models that force images into fixed-size square inputs (distorting aspect ratios or wasting tokens on padding), Gemma 4 scales and snaps images to an exact grid while strictly preserving their natural proportions.
20
+
21
+ This tool is meant to visualize the effect of the macro block sizing and the resulting token count for any image and any token budget $N_{\max} \in \{70, 140, 280, 560, 1120\}$.
22
+
23
+ ## 📦 Hosting on Hugging Face Spaces
24
+
25
+ This project is pre-configured as a **Hugging Face Static Space** (`sdk: static`). To deploy or clone to your own Space:
26
+
27
+ 1. Create a new Space on [Hugging Face Spaces](https://huggingface.co/spaces) and select **Static SDK** (`HTML/JS/CSS`).
28
+ 2. Push the files in this folder directly to your Space repository:
29
+ ```bash
30
+ git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
31
+ cp -r * /path/to/YOUR_SPACE_NAME/
32
+ cd /path/to/YOUR_SPACE_NAME
33
+ git add .
34
+ git commit -m "Deploy Gemma 4 Vision Budget Visualizer"
35
+ git push
36
+ ```
37
+ 3. Your Space will immediately build and serve `index.html` worldwide!
38
+
39
+ ---
40
+
41
+ ## 💻 Running Locally
42
+
43
+ Simply serve the directory with any local static HTTP server or open `index.html` in your browser:
44
+
45
+ ```bash
46
+ # Using Python 3 built-in server:
47
+ python -m http.server 8000
48
+
49
+ # Using Node (npx serve):
50
+ npx serve .
51
+ ```
52
+ Then navigate to `http://localhost:8000` in your web browser.
index.html CHANGED
@@ -1,19 +1,765 @@
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>Gemma 4 - Vision Token Budget</title>
7
+ <!-- Clean, Modern Typography -->
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+
12
+ <style>
13
+ :root {
14
+ --bg-page: #f8fafc;
15
+ --bg-card: #ffffff;
16
+ --text-main: #1e293b;
17
+ --text-sub: #475569;
18
+ --text-muted: #64748b;
19
+ --border-color: #e2e8f0;
20
+ --border-hover: #cbd5e1;
21
+ --accent: #2563eb;
22
+ --accent-light: #eff6ff;
23
+ --radius: 10px;
24
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
25
+ }
26
+
27
+ * {
28
+ box-sizing: border-box;
29
+ margin: 0;
30
+ padding: 0;
31
+ }
32
+
33
+ body {
34
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
35
+ background-color: var(--bg-page);
36
+ color: var(--text-main);
37
+ line-height: 1.6;
38
+ padding: 40px 20px;
39
+ }
40
+
41
+ .header-wrapper {
42
+ max-width: 1100px;
43
+ margin: 0 auto;
44
+ }
45
+
46
+ /* Header */
47
+ header {
48
+ text-align: center;
49
+ margin-bottom: 35px;
50
+ }
51
+
52
+ h1 {
53
+ font-size: 2.2rem;
54
+ font-weight: 700;
55
+ color: var(--text-main);
56
+ letter-spacing: -0.02em;
57
+ }
58
+
59
+ .subtitle {
60
+ font-size: 1.1rem;
61
+ color: var(--text-sub);
62
+ margin-top: 6px;
63
+ font-weight: 500;
64
+ }
65
+
66
+ /* Cards & Containers */
67
+ .card {
68
+ background-color: var(--bg-card);
69
+ border: 1px solid var(--border-color);
70
+ border-radius: var(--radius);
71
+ padding: 24px;
72
+ margin-bottom: 24px;
73
+ box-shadow: var(--shadow);
74
+ }
75
+
76
+ /* Explanation Details Section */
77
+ details.explanation-card {
78
+ background-color: var(--bg-card);
79
+ border: 1px solid var(--border-color);
80
+ border-radius: var(--radius);
81
+ padding: 16px 20px;
82
+ margin-bottom: 28px;
83
+ box-shadow: var(--shadow);
84
+ }
85
+
86
+ details.explanation-card summary {
87
+ cursor: pointer;
88
+ font-weight: 600;
89
+ font-size: 1.05rem;
90
+ color: var(--text-main);
91
+ user-select: none;
92
+ }
93
+
94
+ details.explanation-card summary:hover {
95
+ color: var(--accent);
96
+ }
97
+
98
+ .explanation-img {
99
+ max-width: 100%;
100
+ height: auto;
101
+ margin-top: 16px;
102
+ border-radius: 6px;
103
+ border: 1px solid var(--border-color);
104
+ display: block;
105
+ }
106
+
107
+ /* Upload & Controls Section */
108
+ .upload-controls {
109
+ display: grid;
110
+ grid-template-columns: 1fr;
111
+ gap: 20px;
112
+ }
113
+
114
+ #drop-zone {
115
+ width: 100%;
116
+ min-height: 160px;
117
+ border: 2px dashed #cbd5e1;
118
+ border-radius: var(--radius);
119
+ display: flex;
120
+ flex-direction: column;
121
+ align-items: center;
122
+ justify-content: center;
123
+ text-align: center;
124
+ padding: 24px;
125
+ background-color: #fcfcfd;
126
+ cursor: pointer;
127
+ transition: all 0.2s ease;
128
+ }
129
+
130
+ #drop-zone:hover, #drop-zone.hover {
131
+ border-color: var(--accent);
132
+ background-color: var(--accent-light);
133
+ }
134
+
135
+ .drop-title {
136
+ font-size: 1.05rem;
137
+ font-weight: 600;
138
+ color: var(--text-main);
139
+ }
140
+
141
+ .drop-sub {
142
+ font-size: 0.85rem;
143
+ color: var(--text-muted);
144
+ margin-top: 4px;
145
+ }
146
+
147
+ /* Presets & Grid Checkbox Bar */
148
+ .presets-and-options {
149
+ display: flex;
150
+ flex-wrap: wrap;
151
+ align-items: center;
152
+ justify-content: space-between;
153
+ gap: 16px;
154
+ padding-top: 16px;
155
+ border-top: 1px solid var(--border-color);
156
+ }
157
+
158
+ .presets-group {
159
+ display: flex;
160
+ flex-wrap: wrap;
161
+ align-items: center;
162
+ gap: 10px;
163
+ }
164
+
165
+ .presets-label {
166
+ font-size: 0.9rem;
167
+ font-weight: 600;
168
+ color: var(--text-sub);
169
+ margin-right: 4px;
170
+ }
171
+
172
+ .preset-btn {
173
+ background-color: #f1f5f9;
174
+ border: 1px solid var(--border-color);
175
+ border-radius: 6px;
176
+ padding: 7px 14px;
177
+ font-size: 0.85rem;
178
+ font-weight: 500;
179
+ color: var(--text-main);
180
+ cursor: pointer;
181
+ transition: all 0.2s ease;
182
+ }
183
+
184
+ .preset-btn:hover {
185
+ background-color: #e2e8f0;
186
+ border-color: #94a3b8;
187
+ }
188
+
189
+ .checkbox-label {
190
+ display: flex;
191
+ align-items: center;
192
+ gap: 8px;
193
+ font-size: 0.9rem;
194
+ font-weight: 600;
195
+ color: var(--text-main);
196
+ cursor: pointer;
197
+ user-select: none;
198
+ }
199
+
200
+ .checkbox-label input[type="checkbox"] {
201
+ width: 18px;
202
+ height: 18px;
203
+ accent-color: var(--accent);
204
+ cursor: pointer;
205
+ }
206
+
207
+ /* Centered Horizontal Menu Bar for N_max */
208
+ .menu-bar {
209
+ display: flex;
210
+ justify-content: center;
211
+ flex-wrap: wrap;
212
+ gap: 10px;
213
+ margin: 30px 0;
214
+ }
215
+
216
+ .menu-btn {
217
+ background-color: var(--bg-card);
218
+ border: 1px solid var(--border-color);
219
+ border-radius: 8px;
220
+ padding: 10px 24px;
221
+ font-size: 1rem;
222
+ font-weight: 600;
223
+ color: var(--text-sub);
224
+ cursor: pointer;
225
+ transition: all 0.2s ease;
226
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
227
+ }
228
+
229
+ .menu-btn:hover {
230
+ color: var(--text-main);
231
+ border-color: #94a3b8;
232
+ background-color: #f8fafc;
233
+ }
234
+
235
+ .menu-btn.active {
236
+ background-color: var(--accent);
237
+ color: #ffffff;
238
+ border-color: var(--accent);
239
+ box-shadow: 0 4px 10px rgba(37, 99, 235, 0.25);
240
+ }
241
+
242
+ /* Results Display Area */
243
+ #results-area {
244
+ display: flex;
245
+ flex-direction: column;
246
+ align-items: center;
247
+ width: 100%;
248
+ overflow: visible;
249
+ }
250
+
251
+ /* Stacked layout centered on page */
252
+ .stacked-list {
253
+ display: flex;
254
+ flex-direction: column;
255
+ align-items: center;
256
+ gap: 36px;
257
+ width: 100%;
258
+ }
259
+
260
+ /* Card expands naturally to show exact, unconstrained width without any inner scrollbars */
261
+ .image-card {
262
+ background-color: var(--bg-card);
263
+ border: 1px solid var(--border-color);
264
+ border-radius: var(--radius);
265
+ padding: 28px 24px;
266
+ box-shadow: var(--shadow);
267
+ display: flex;
268
+ flex-direction: column;
269
+ align-items: center;
270
+ width: auto;
271
+ min-width: 650px;
272
+ max-width: none;
273
+ }
274
+
275
+ .image-card h3 {
276
+ font-size: 1.3rem;
277
+ font-weight: 700;
278
+ color: var(--text-main);
279
+ margin-bottom: 20px;
280
+ width: 100%;
281
+ text-align: center;
282
+ border-bottom: 1px solid var(--border-color);
283
+ padding-bottom: 14px;
284
+ }
285
+
286
+ /* Canvas wrapper allows true actual resolution display without scroll constraints */
287
+ .canvas-box {
288
+ position: relative;
289
+ display: flex;
290
+ align-items: center;
291
+ justify-content: center;
292
+ margin: 10px auto 24px;
293
+ overflow: visible;
294
+ width: auto;
295
+ max-width: none;
296
+ }
297
+
298
+ canvas {
299
+ border: 1px solid #cbd5e1;
300
+ border-radius: 6px;
301
+ display: block;
302
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
303
+ /* True 1:1 pixel accuracy is applied via style in JS */
304
+ }
305
+
306
+ /* Grid Overlay Canvas */
307
+ .grid-overlay {
308
+ position: absolute;
309
+ top: 0;
310
+ left: 0;
311
+ pointer-events: none;
312
+ border-radius: 6px;
313
+ }
314
+
315
+ /* Tables */
316
+ .info-table {
317
+ width: 100%;
318
+ max-width: 650px;
319
+ margin: 0 auto;
320
+ border-collapse: collapse;
321
+ border: 1px solid var(--border-color);
322
+ border-radius: 6px;
323
+ overflow: hidden;
324
+ font-size: 0.95rem;
325
+ }
326
+
327
+ .info-table th,
328
+ .info-table td {
329
+ text-align: left;
330
+ padding: 12px 16px;
331
+ border-bottom: 1px solid var(--border-color);
332
+ }
333
+
334
+ .info-table tr:last-child th,
335
+ .info-table tr:last-child td {
336
+ border-bottom: none;
337
+ }
338
+
339
+ .info-table th {
340
+ background-color: #f8fafc;
341
+ font-weight: 600;
342
+ color: var(--text-sub);
343
+ width: 55%;
344
+ }
345
+
346
+ .info-table td {
347
+ color: var(--text-main);
348
+ font-weight: 500;
349
+ }
350
+
351
+ /* Empty state */
352
+ .empty-message {
353
+ text-align: center;
354
+ padding: 50px 20px;
355
+ color: var(--text-muted);
356
+ font-size: 1.05rem;
357
+ background-color: var(--bg-card);
358
+ border: 1px dashed #cbd5e1;
359
+ border-radius: var(--radius);
360
+ width: 100%;
361
+ max-width: 1100px;
362
+ }
363
+ </style>
364
+ </head>
365
+ <body>
366
+
367
+ <!-- Header & Controls bounded nicely -->
368
+ <div class="header-wrapper">
369
+ <header>
370
+ <h1>Gemma 4 - Vision Token Budget</h1>
371
+ <p class="subtitle">Aspect-Ratio Preserving Image Resizing</p>
372
+ </header>
373
+
374
+ <!-- Explanation Details Section with image.png / sample_paper.png -->
375
+ <details class="explanation-card">
376
+ <summary>Explanation Aspect Preserving Resize to Sequence Length (a.k.a. Visual Budget)</summary>
377
+ <img src="sample_paper.png" onerror="this.onerror=null; this.src='image.png';" alt="Explanation Aspect Preserving Resize to Sequence Length (a.k.a. Visual Budget)" class="explanation-img">
378
+ </details>
379
+
380
+ <!-- Upload & Controls Card -->
381
+ <section class="card upload-controls">
382
+ <div id="drop-zone">
383
+ <div class="drop-title">Drag & Drop an image here, or Paste (Ctrl+V) from clipboard</div>
384
+ <div class="drop-sub">Click anywhere in this box to browse local files</div>
385
+ </div>
386
+
387
+ <div class="presets-and-options">
388
+ <div class="presets-group" id="preset-buttons-container">
389
+ <span class="presets-label">Presets:</span>
390
+ <!-- Populated dynamically -->
391
+ </div>
392
+
393
+ <label class="checkbox-label">
394
+ <input type="checkbox" id="toggle-grid" checked>
395
+ <span>Show 48 × 48 px Token Grid</span>
396
+ </label>
397
+ </div>
398
+ </section>
399
+
400
+ <!-- Centered Horizontal Menu Bar for N_max -->
401
+ <nav class="menu-bar" id="menu-bar">
402
+ <button class="menu-btn" data-budget="70">70</button>
403
+ <button class="menu-btn" data-budget="140">140</button>
404
+ <button class="menu-btn" data-budget="280">280</button>
405
+ <button class="menu-btn" data-budget="560">560</button>
406
+ <button class="menu-btn" data-budget="1120">1120</button>
407
+ <button class="menu-btn active" data-budget="All">All</button>
408
+ </nav>
409
+ </div>
410
+
411
+ <!-- Results Area expands naturally for unconstrained true resolution -->
412
+ <main id="results-area">
413
+ <div class="empty-message" id="empty-message">
414
+ Please upload or select an image to visualize the aspect-ratio preserving resize.
415
+ </div>
416
+ <div id="display-content" style="width: 100%;"></div>
417
+ </main>
418
+
419
+ <script>
420
+ // Gemma 4 Macro-Block Constants
421
+ const P = 16;
422
+ const K = 3;
423
+ const M = P * K; // 48
424
+ const BUDGETS = [70, 140, 280, 560, 1120];
425
+
426
+ // State
427
+ let currentImage = null;
428
+ let selectedOption = 'All'; // Default N_max option
429
+ let showGridOverlay = true;
430
+ let processedResults = {};
431
+
432
+ // DOM Elements
433
+ const dropZone = document.getElementById('drop-zone');
434
+ const presetButtonsContainer = document.getElementById('preset-buttons-container');
435
+ const toggleGridCheckbox = document.getElementById('toggle-grid');
436
+ const menuBar = document.getElementById('menu-bar');
437
+ const emptyMessage = document.getElementById('empty-message');
438
+ const displayContent = document.getElementById('display-content');
439
+
440
+ // Preset configurations
441
+ const PRESETS = [
442
+ { name: 'Sample Paper', src: 'sample_paper.png', fallback: 'image.png' },
443
+ { name: 'Sample 12B', src: 'sample_12b.png' }
444
+ ];
445
+
446
+ function init() {
447
+ setupPresets();
448
+ setupEventListeners();
449
+ // Load initial preset
450
+ loadPreset(PRESETS[0]);
451
+ }
452
+
453
+ function setupPresets() {
454
+ presetButtonsContainer.replaceChildren();
455
+
456
+ PRESETS.forEach(preset => {
457
+ const btn = document.createElement('button');
458
+ btn.className = 'preset-btn';
459
+ btn.textContent = preset.name;
460
+ btn.addEventListener('click', () => loadPreset(preset));
461
+ presetButtonsContainer.appendChild(btn);
462
+ });
463
+ }
464
+
465
+ function setupEventListeners() {
466
+ // Drag over
467
+ dropZone.addEventListener('dragover', (e) => {
468
+ e.preventDefault();
469
+ dropZone.classList.add('hover');
470
+ });
471
+
472
+ dropZone.addEventListener('dragleave', () => {
473
+ dropZone.classList.remove('hover');
474
+ });
475
+
476
+ dropZone.addEventListener('drop', (e) => {
477
+ e.preventDefault();
478
+ dropZone.classList.remove('hover');
479
+ const files = e.dataTransfer.files;
480
+ if (files.length > 0) {
481
+ handleFile(files[0]);
482
+ }
483
+ });
484
+
485
+ // Click dropzone
486
+ dropZone.addEventListener('click', () => {
487
+ const input = document.createElement('input');
488
+ input.type = 'file';
489
+ input.accept = 'image/*';
490
+ input.onchange = (e) => {
491
+ if (e.target.files.length > 0) {
492
+ handleFile(e.target.files[0]);
493
+ }
494
+ };
495
+ input.click();
496
+ });
497
+
498
+ // Paste
499
+ document.addEventListener('paste', (e) => {
500
+ const items = (e.clipboardData || e.originalEvent.clipboardData).items;
501
+ for (let i = 0; i < items.length; i++) {
502
+ if (items[i].type.indexOf('image') !== -1) {
503
+ const file = items[i].getAsFile();
504
+ handleFile(file);
505
+ break;
506
+ }
507
+ }
508
+ });
509
+
510
+ // Menu Bar clicks
511
+ menuBar.addEventListener('click', (e) => {
512
+ if (e.target.classList.contains('menu-btn')) {
513
+ const budget = e.target.getAttribute('data-budget');
514
+ selectMenuOption(budget);
515
+ }
516
+ });
517
+
518
+ // Grid checkbox
519
+ toggleGridCheckbox.addEventListener('change', (e) => {
520
+ showGridOverlay = e.target.checked;
521
+ renderResults();
522
+ });
523
+ }
524
+
525
+ function handleFile(file) {
526
+ if (!file.type.startsWith('image/')) {
527
+ alert('Not an image file');
528
+ return;
529
+ }
530
+ const reader = new FileReader();
531
+ reader.onload = (e) => {
532
+ const img = new Image();
533
+ img.onload = () => processImage(img);
534
+ img.src = e.target.result;
535
+ };
536
+ reader.readAsDataURL(file);
537
+ }
538
+
539
+ function loadPreset(preset) {
540
+ if (preset.src) {
541
+ const img = new Image();
542
+ img.onload = () => processImage(img);
543
+ img.onerror = () => {
544
+ if (preset.fallback) {
545
+ const fbImg = new Image();
546
+ fbImg.onload = () => processImage(fbImg);
547
+ fbImg.onerror = () => alert('Please ensure image files (sample_paper.png or image.png) are uploaded to your static hosting directory.');
548
+ fbImg.src = preset.fallback;
549
+ } else {
550
+ alert('Please ensure ' + preset.src + ' is uploaded to your static hosting directory.');
551
+ }
552
+ };
553
+ img.src = preset.src;
554
+ }
555
+ }
556
+
557
+ function processImage(img) {
558
+ currentImage = img;
559
+ processedResults = {};
560
+
561
+ BUDGETS.forEach(nMax => {
562
+ processedResults[nMax] = resizeImage(img, nMax);
563
+ });
564
+
565
+ emptyMessage.style.display = 'none';
566
+ renderResults();
567
+ }
568
+
569
+ function resizeImage(img, nMax) {
570
+ const w = img.naturalWidth;
571
+ const h = img.naturalHeight;
572
+
573
+ const m = M;
574
+ const t = nMax * m * m;
575
+ const f = Math.sqrt(t / (h * w));
576
+ const hIdeal = f * h;
577
+ const wIdeal = f * w;
578
+
579
+ let hTarget = Math.floor(hIdeal / m) * m;
580
+ let wTarget = Math.floor(wIdeal / m) * m;
581
+
582
+ hTarget = Math.max(m, hTarget);
583
+ wTarget = Math.max(m, wTarget);
584
+
585
+ const canvas = document.createElement('canvas');
586
+ canvas.width = wTarget;
587
+ canvas.height = hTarget;
588
+ const ctx = canvas.getContext('2d');
589
+
590
+ ctx.imageSmoothingEnabled = true;
591
+ ctx.imageSmoothingQuality = 'high';
592
+ ctx.drawImage(img, 0, 0, wTarget, hTarget);
593
+
594
+ const targetPatches = (hTarget / P) * (wTarget / P);
595
+ const actualTokens = Math.min(Math.floor(targetPatches / (K * K)), nMax);
596
+
597
+ return {
598
+ canvas: canvas,
599
+ width: wTarget,
600
+ height: hTarget,
601
+ actualTokens: actualTokens
602
+ };
603
+ }
604
+
605
+ function selectMenuOption(option) {
606
+ selectedOption = option;
607
+
608
+ const buttons = menuBar.querySelectorAll('.menu-btn');
609
+ buttons.forEach(btn => {
610
+ if (btn.getAttribute('data-budget') === option) {
611
+ btn.classList.add('active');
612
+ } else {
613
+ btn.classList.remove('active');
614
+ }
615
+ });
616
+
617
+ renderResults();
618
+ }
619
+
620
+ function renderResults() {
621
+ if (!currentImage) return;
622
+ displayContent.replaceChildren();
623
+
624
+ const w = currentImage.naturalWidth;
625
+ const h = currentImage.naturalHeight;
626
+ const origNMax = (w * h) / (M * M);
627
+ const origTokens = Math.floor(w / M) * Math.floor(h / M);
628
+
629
+ const stackedDiv = document.createElement('div');
630
+ stackedDiv.className = 'stacked-list';
631
+
632
+ if (selectedOption === 'All') {
633
+ BUDGETS.forEach(nMax => {
634
+ const data = processedResults[nMax];
635
+ const card = createCardElement(
636
+ `N_max = ${nMax}`,
637
+ data.canvas,
638
+ data.width,
639
+ data.height,
640
+ [
641
+ { label: 'Target Dimensions', value: `${data.width} x ${data.height}` },
642
+ { label: 'Actual Tokens', value: `${data.actualTokens}` }
643
+ ],
644
+ true
645
+ );
646
+ stackedDiv.appendChild(card);
647
+ });
648
+ } else {
649
+ const nMax = parseInt(selectedOption, 10);
650
+ const data = processedResults[nMax];
651
+ const resizedCard = createCardElement(
652
+ `N_max = ${nMax}`,
653
+ data.canvas,
654
+ data.width,
655
+ data.height,
656
+ [
657
+ { label: 'Target Dimensions', value: `${data.width} x ${data.height}` },
658
+ { label: 'Actual Tokens', value: `${data.actualTokens}` }
659
+ ],
660
+ true
661
+ );
662
+ stackedDiv.appendChild(resizedCard);
663
+ }
664
+
665
+ // Create Original Card (always full actual resolution centered without constraints, no grid overlay) and place at the bottom
666
+ const origCard = createCardElement(
667
+ 'Original Image',
668
+ currentImage,
669
+ w,
670
+ h,
671
+ [
672
+ { label: 'Dimensions', value: `${w} x ${h}` },
673
+ { label: 'Equivalent N_max (w*h/m²)', value: origNMax.toFixed(2) },
674
+ { label: 'Actual Tokens (floor(w/m)*floor(h/m))', value: `${origTokens}` }
675
+ ],
676
+ false
677
+ );
678
+ stackedDiv.appendChild(origCard);
679
+
680
+ displayContent.appendChild(stackedDiv);
681
+ }
682
+
683
+ function createCardElement(titleText, imgOrCanvas, width, height, tableRows, allowGrid = true) {
684
+ const card = document.createElement('div');
685
+ card.className = 'image-card';
686
+
687
+ const h3 = document.createElement('h3');
688
+ h3.textContent = titleText;
689
+ card.appendChild(h3);
690
+
691
+ const canvasBox = document.createElement('div');
692
+ canvasBox.className = 'canvas-box';
693
+
694
+ // Clone or draw image/canvas to a display canvas with true actual pixel dimensions unconstrained
695
+ const displayCanvas = document.createElement('canvas');
696
+ displayCanvas.width = width;
697
+ displayCanvas.height = height;
698
+ displayCanvas.style.width = width + 'px';
699
+ displayCanvas.style.height = height + 'px';
700
+ const ctx = displayCanvas.getContext('2d');
701
+ ctx.imageSmoothingEnabled = true;
702
+ ctx.imageSmoothingQuality = 'high';
703
+ ctx.drawImage(imgOrCanvas, 0, 0, width, height);
704
+
705
+ canvasBox.appendChild(displayCanvas);
706
+
707
+ if (showGridOverlay && allowGrid) {
708
+ const overlay = createGridOverlay(width, height);
709
+ canvasBox.appendChild(overlay);
710
+ }
711
+
712
+ card.appendChild(canvasBox);
713
+
714
+ // Create Table using safe DOM methods
715
+ const table = document.createElement('table');
716
+ table.className = 'info-table';
717
+
718
+ tableRows.forEach(row => {
719
+ const tr = document.createElement('tr');
720
+ const th = document.createElement('th');
721
+ th.textContent = row.label;
722
+ const td = document.createElement('td');
723
+ td.textContent = row.value;
724
+ tr.appendChild(th);
725
+ tr.appendChild(td);
726
+ table.appendChild(tr);
727
+ });
728
+
729
+ card.appendChild(table);
730
+ return card;
731
+ }
732
+
733
+ function createGridOverlay(width, height) {
734
+ const canvas = document.createElement('canvas');
735
+ canvas.className = 'grid-overlay';
736
+ canvas.width = width;
737
+ canvas.height = height;
738
+ canvas.style.width = width + 'px';
739
+ canvas.style.height = height + 'px';
740
+ const ctx = canvas.getContext('2d');
741
+
742
+ ctx.strokeStyle = 'rgba(239, 68, 68, 0.75)'; // Bright Red Grid
743
+ ctx.lineWidth = 1.25;
744
+
745
+ for (let x = 0; x <= width; x += M) {
746
+ ctx.beginPath();
747
+ ctx.moveTo(x, 0);
748
+ ctx.lineTo(x, height);
749
+ ctx.stroke();
750
+ }
751
+
752
+ for (let y = 0; y <= height; y += M) {
753
+ ctx.beginPath();
754
+ ctx.moveTo(0, y);
755
+ ctx.lineTo(width, y);
756
+ ctx.stroke();
757
+ }
758
+
759
+ return canvas;
760
+ }
761
+
762
+ window.addEventListener('DOMContentLoaded', init);
763
+ </script>
764
+ </body>
765
+ </html>
sample_12b.png ADDED

Git LFS Details

  • SHA256: b1ec60bf9e2321060ef318227b6e89448d19b2e2df520bdd203e22ab319ef4fd
  • Pointer size: 131 Bytes
  • Size of remote file: 305 kB
sample_paper.png ADDED

Git LFS Details

  • SHA256: f277e66ae233f8a8c7eb7c1e73947039da115d1853fde6573fe38b1899659997
  • Pointer size: 132 Bytes
  • Size of remote file: 1.56 MB