Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -3,22 +3,23 @@ import uuid
|
|
3 |
import base64
|
4 |
import io
|
5 |
import random
|
|
|
6 |
from flask import Flask, request, render_template_string, send_from_directory, url_for
|
7 |
from PIL import Image, ImageDraw, ImageFont
|
8 |
import numpy as np
|
9 |
|
10 |
# --- Configuration ---
|
11 |
-
# Use os.getenv("PORT", 5000) for compatibility with services like Heroku/Hugging Face
|
12 |
-
# The host must be '0.0.0.0' to be accessible within the Docker container
|
13 |
HOST = '0.0.0.0'
|
14 |
-
PORT = 7860
|
15 |
-
UPLOAD_FOLDER = '
|
16 |
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
17 |
|
18 |
app = Flask(__name__)
|
19 |
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
20 |
|
21 |
-
# --- Image Processing Logic (
|
|
|
|
|
22 |
|
23 |
def process_image_for_grid(image_pil, grid_size):
|
24 |
img_width, img_height = image_pil.size
|
@@ -71,9 +72,8 @@ def pil_to_base64(pil_image):
|
|
71 |
pil_image.save(buffered, format="PNG")
|
72 |
return base64.b64encode(buffered.getvalue()).decode("utf-8")
|
73 |
|
74 |
-
# --- HTML Templates ---
|
75 |
|
76 |
-
# Using render_template_string to keep everything in one file
|
77 |
HOME_PAGE_TEMPLATE = """
|
78 |
<!DOCTYPE html>
|
79 |
<html lang="en">
|
@@ -82,40 +82,95 @@ HOME_PAGE_TEMPLATE = """
|
|
82 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
83 |
<title>Secure Image Scrambler</title>
|
84 |
<style>
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
</style>
|
95 |
</head>
|
96 |
<body>
|
97 |
<div class="container">
|
98 |
<h1>🖼️ Secure Image Scrambler</h1>
|
99 |
-
<p>Upload an image to
|
100 |
<form action="/process" method="post" enctype="multipart/form-data">
|
101 |
<div>
|
102 |
-
<label
|
103 |
-
<
|
|
|
|
|
|
|
104 |
</div>
|
105 |
<div>
|
106 |
<label for="grid_size">Grid Size (e.g., 8 for 8x8):</label>
|
107 |
-
<input type="number" id="grid_size" name="grid_size" value="
|
108 |
</div>
|
109 |
<div>
|
110 |
-
<label for="seed">Scramble Seed (
|
111 |
<input type="number" id="seed" name="seed" value="{{ random_seed }}" required>
|
112 |
</div>
|
113 |
<button type="submit">Scramble Image</button>
|
114 |
</form>
|
115 |
{% if error %}
|
116 |
-
<p class="error">
|
117 |
{% endif %}
|
118 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
</body>
|
120 |
</html>
|
121 |
"""
|
@@ -128,70 +183,66 @@ RESULTS_PAGE_TEMPLATE = """
|
|
128 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
129 |
<title>Scrambled Results</title>
|
130 |
<style>
|
131 |
-
|
132 |
-
|
133 |
-
|
|
|
|
|
134 |
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 2rem; }
|
135 |
-
.card { background: #fff; padding: 1.5rem; border-radius:
|
136 |
-
h2 { border-bottom:
|
137 |
-
p {
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
|
|
|
|
142 |
</style>
|
143 |
</head>
|
144 |
<body>
|
145 |
<div class="container">
|
146 |
-
<
|
|
|
|
|
|
|
147 |
<div class="grid">
|
148 |
<div class="card">
|
149 |
<h2>Unscrambled Preview (Protected)</h2>
|
150 |
-
<
|
151 |
-
<div class="canvas-container">
|
152 |
<canvas id="unscrambled-canvas" width="{{ width }}" height="{{ height }}"></canvas>
|
153 |
</div>
|
|
|
154 |
</div>
|
155 |
<div class="card">
|
156 |
-
<h2>Scrambled Image
|
157 |
-
<
|
158 |
-
<a href="{{ scrambled_image_url }}" download>
|
159 |
<img src="{{ scrambled_image_url }}" alt="Scrambled Image">
|
160 |
-
</
|
|
|
|
|
161 |
</div>
|
162 |
<div class="card">
|
163 |
-
<h2>Scrambling Map</h2>
|
164 |
-
<
|
165 |
-
|
166 |
-
|
167 |
-
|
|
|
168 |
</div>
|
169 |
</div>
|
170 |
-
<
|
|
|
|
|
171 |
</div>
|
172 |
|
173 |
<script>
|
174 |
-
// This script is guaranteed to run after the canvas element exists in the DOM.
|
175 |
const canvas = document.getElementById('unscrambled-canvas');
|
176 |
const ctx = canvas.getContext('2d');
|
177 |
const img = new Image();
|
178 |
-
|
179 |
-
// Prevent right-click context menu on the canvas.
|
180 |
canvas.oncontextmenu = (e) => { e.preventDefault(); return false; };
|
181 |
-
|
182 |
-
// The image data is embedded directly here from the server.
|
183 |
img.src = "data:image/png;base64,{{ unscrambled_base64 }}";
|
184 |
-
|
185 |
-
img.onload = () => {
|
186 |
-
// Draw the loaded image onto the canvas once it's ready.
|
187 |
-
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
188 |
-
};
|
189 |
-
img.onerror = () => {
|
190 |
-
ctx.font = "16px Arial";
|
191 |
-
ctx.fillStyle = "red";
|
192 |
-
ctx.textAlign = "center";
|
193 |
-
ctx.fillText("Error loading preview.", canvas.width / 2, canvas.height / 2);
|
194 |
-
};
|
195 |
</script>
|
196 |
</body>
|
197 |
</html>
|
@@ -202,65 +253,73 @@ RESULTS_PAGE_TEMPLATE = """
|
|
202 |
@app.route('/')
|
203 |
def home():
|
204 |
"""Renders the main upload page."""
|
205 |
-
return render_template_string(HOME_PAGE_TEMPLATE, random_seed=random.randint(
|
206 |
|
207 |
@app.route('/process', methods=['POST'])
|
208 |
def process_image():
|
209 |
"""Handles the image processing."""
|
210 |
-
if 'image_file' not in request.files:
|
211 |
-
return render_template_string(HOME_PAGE_TEMPLATE, error="No file
|
212 |
-
|
213 |
-
file = request.files['image_file']
|
214 |
-
if file.filename == '':
|
215 |
-
return render_template_string(HOME_PAGE_TEMPLATE, error="No file selected.")
|
216 |
-
|
217 |
-
if file:
|
218 |
-
try:
|
219 |
-
grid_size = int(request.form.get('grid_size', 8))
|
220 |
-
seed = int(request.form.get('seed', 12345))
|
221 |
-
|
222 |
-
input_image = Image.open(file.stream).convert("RGB")
|
223 |
-
|
224 |
-
# 1. Scramble the image
|
225 |
-
scrambled_img, scramble_map = scramble_image(input_image, grid_size, seed)
|
226 |
-
if scrambled_img is None:
|
227 |
-
return render_template_string(HOME_PAGE_TEMPLATE, error=f"Image is too small for a {grid_size}x{grid_size} grid. Try a larger image or smaller grid.")
|
228 |
-
|
229 |
-
# 2. Create the map visualization
|
230 |
-
map_viz_img = create_mapping_visualization(scramble_map, grid_size)
|
231 |
-
|
232 |
-
# 3. Unscramble the image in memory for the canvas preview
|
233 |
-
unscrambled_img = unscramble_image(scrambled_img, scramble_map, grid_size)
|
234 |
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
|
|
|
|
|
|
|
|
|
|
241 |
|
242 |
-
|
243 |
-
|
|
|
|
|
|
|
|
|
244 |
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
|
|
254 |
|
255 |
-
|
256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
257 |
|
258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
259 |
|
|
|
|
|
260 |
|
261 |
-
@app.route('/
|
262 |
-
def
|
263 |
-
"""Serves the generated
|
264 |
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
265 |
|
266 |
if __name__ == '__main__':
|
|
|
3 |
import base64
|
4 |
import io
|
5 |
import random
|
6 |
+
import json
|
7 |
from flask import Flask, request, render_template_string, send_from_directory, url_for
|
8 |
from PIL import Image, ImageDraw, ImageFont
|
9 |
import numpy as np
|
10 |
|
11 |
# --- Configuration ---
|
|
|
|
|
12 |
HOST = '0.0.0.0'
|
13 |
+
PORT = 7860
|
14 |
+
UPLOAD_FOLDER = 'temp_files'
|
15 |
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
16 |
|
17 |
app = Flask(__name__)
|
18 |
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
19 |
|
20 |
+
# --- Image Processing Logic (Unchanged) ---
|
21 |
+
# ... (The image processing functions are the same, so they are collapsed here for brevity)
|
22 |
+
# You can copy them from the previous Flask answer. For clarity, they are included here again.
|
23 |
|
24 |
def process_image_for_grid(image_pil, grid_size):
|
25 |
img_width, img_height = image_pil.size
|
|
|
72 |
pil_image.save(buffered, format="PNG")
|
73 |
return base64.b64encode(buffered.getvalue()).decode("utf-8")
|
74 |
|
75 |
+
# --- HTML Templates with Upgraded UI/UX ---
|
76 |
|
|
|
77 |
HOME_PAGE_TEMPLATE = """
|
78 |
<!DOCTYPE html>
|
79 |
<html lang="en">
|
|
|
82 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
83 |
<title>Secure Image Scrambler</title>
|
84 |
<style>
|
85 |
+
:root { --primary-color: #3b82f6; --hover-color: #2563eb; --bg-color: #f8fafc; --text-color: #334155; --border-color: #cbd5e1; }
|
86 |
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; background-color: var(--bg-color); color: var(--text-color); margin: 0; padding: 2rem; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
|
87 |
+
.container { max-width: 650px; width: 100%; margin: auto; background: #fff; padding: 2.5rem; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); }
|
88 |
+
h1 { color: #1e293b; text-align: center; margin-bottom: 0.5rem; }
|
89 |
+
.subtitle { text-align: center; color: #64748b; margin-bottom: 2.5rem; }
|
90 |
+
form > div { margin-bottom: 1.5rem; }
|
91 |
+
label { display: block; margin-bottom: 0.5rem; font-weight: 600; color: #475569; }
|
92 |
+
input[type="number"] { width: 100%; padding: 0.75rem; border: 1px solid var(--border-color); border-radius: 8px; box-sizing: border-box; font-size: 1rem; transition: border-color 0.2s, box-shadow 0.2s; }
|
93 |
+
input[type="number"]:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); outline: none; }
|
94 |
+
button { width: 100%; padding: 0.85rem; background-color: var(--primary-color); color: white; border: none; border-radius: 8px; font-size: 1.1rem; font-weight: 600; cursor: pointer; transition: background-color 0.2s; }
|
95 |
+
button:hover { background-color: var(--hover-color); }
|
96 |
+
.error { color: #ef4444; background-color: #fee2e2; border: 1px solid #fecaca; padding: 1rem; border-radius: 8px; margin-top: 1.5rem; text-align: center; }
|
97 |
+
/* Drop Zone Styles */
|
98 |
+
.drop-zone { border: 2px dashed var(--border-color); border-radius: 12px; padding: 40px; text-align: center; cursor: pointer; transition: border-color 0.3s, background-color 0.3s; }
|
99 |
+
.drop-zone--over { border-color: var(--primary-color); background-color: #eff6ff; }
|
100 |
+
.drop-zone__input { display: none; }
|
101 |
+
.drop-zone__prompt { color: #64748b; font-size: 1.1rem; }
|
102 |
+
.drop-zone__prompt strong { color: var(--primary-color); }
|
103 |
</style>
|
104 |
</head>
|
105 |
<body>
|
106 |
<div class="container">
|
107 |
<h1>🖼️ Secure Image Scrambler</h1>
|
108 |
+
<p class="subtitle">Upload an image to create a scrambled, downloadable version and a protected, non-downloadable preview.</p>
|
109 |
<form action="/process" method="post" enctype="multipart/form-data">
|
110 |
<div>
|
111 |
+
<label>Upload Image</label>
|
112 |
+
<div class="drop-zone">
|
113 |
+
<span class="drop-zone__prompt">Drag & drop an image here, or <strong>click to select</strong></span>
|
114 |
+
<input type="file" name="image_file" class="drop-zone__input" accept="image/*" required>
|
115 |
+
</div>
|
116 |
</div>
|
117 |
<div>
|
118 |
<label for="grid_size">Grid Size (e.g., 8 for 8x8):</label>
|
119 |
+
<input type="number" id="grid_size" name="grid_size" value="16" min="2" max="64" required>
|
120 |
</div>
|
121 |
<div>
|
122 |
+
<label for="seed">Scramble Seed (for reproducibility):</label>
|
123 |
<input type="number" id="seed" name="seed" value="{{ random_seed }}" required>
|
124 |
</div>
|
125 |
<button type="submit">Scramble Image</button>
|
126 |
</form>
|
127 |
{% if error %}
|
128 |
+
<p class="error">{{ error }}</p>
|
129 |
{% endif %}
|
130 |
</div>
|
131 |
+
|
132 |
+
<script>
|
133 |
+
document.querySelectorAll(".drop-zone__input").forEach(inputElement => {
|
134 |
+
const dropZoneElement = inputElement.closest(".drop-zone");
|
135 |
+
|
136 |
+
dropZoneElement.addEventListener("click", e => {
|
137 |
+
inputElement.click();
|
138 |
+
});
|
139 |
+
|
140 |
+
inputElement.addEventListener("change", e => {
|
141 |
+
if (inputElement.files.length) {
|
142 |
+
updateThumbnail(dropZoneElement, inputElement.files[0]);
|
143 |
+
}
|
144 |
+
});
|
145 |
+
|
146 |
+
dropZoneElement.addEventListener("dragover", e => {
|
147 |
+
e.preventDefault();
|
148 |
+
dropZoneElement.classList.add("drop-zone--over");
|
149 |
+
});
|
150 |
+
|
151 |
+
["dragleave", "dragend"].forEach(type => {
|
152 |
+
dropZoneElement.addEventListener(type, e => {
|
153 |
+
dropZoneElement.classList.remove("drop-zone--over");
|
154 |
+
});
|
155 |
+
});
|
156 |
+
|
157 |
+
dropZoneElement.addEventListener("drop", e => {
|
158 |
+
e.preventDefault();
|
159 |
+
if (e.dataTransfer.files.length) {
|
160 |
+
inputElement.files = e.dataTransfer.files;
|
161 |
+
updateThumbnail(dropZoneElement, e.dataTransfer.files[0]);
|
162 |
+
}
|
163 |
+
dropZoneElement.classList.remove("drop-zone--over");
|
164 |
+
});
|
165 |
+
});
|
166 |
+
|
167 |
+
function updateThumbnail(dropZoneElement, file) {
|
168 |
+
let promptElement = dropZoneElement.querySelector(".drop-zone__prompt");
|
169 |
+
if (promptElement) {
|
170 |
+
promptElement.textContent = `Selected: ${file.name}`;
|
171 |
+
}
|
172 |
+
}
|
173 |
+
</script>
|
174 |
</body>
|
175 |
</html>
|
176 |
"""
|
|
|
183 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
184 |
<title>Scrambled Results</title>
|
185 |
<style>
|
186 |
+
:root { --primary-color: #3b82f6; --bg-color: #f8fafc; --text-color: #334155; }
|
187 |
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; background-color: var(--bg-color); color: var(--text-color); margin: 0; padding: 2rem; }
|
188 |
+
.container { max-width: 1400px; margin: auto; }
|
189 |
+
.header { text-align: center; margin-bottom: 3rem; }
|
190 |
+
h1 { color: #1e293b; margin-bottom: 0.5rem; }
|
191 |
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 2rem; }
|
192 |
+
.card { background: #fff; padding: 1.5rem; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); display: flex; flex-direction: column; }
|
193 |
+
h2 { border-bottom: 1px solid #e2e8f0; padding-bottom: 0.75rem; margin-top: 0; font-size: 1.25rem; color: #1e293b; }
|
194 |
+
.card p { color: #64748b; flex-grow: 1; }
|
195 |
+
.image-container { margin-bottom: 1.5rem; text-align: center; }
|
196 |
+
img, canvas { max-width: 100%; height: auto; border: 1px solid #e2e8f0; border-radius: 8px; image-rendering: pixelated; }
|
197 |
+
.canvas-container { background: repeating-conic-gradient(#f1f5f9 0% 25%, transparent 0% 50%) 50% / 20px 20px; border-radius: 8px; }
|
198 |
+
.download-button, .home-link { display: block; width: 100%; text-align: center; padding: 0.75rem; background-color: var(--primary-color); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; transition: background-color 0.2s; box-sizing: border-box; }
|
199 |
+
.download-button:hover, .home-link:hover { background-color: #2563eb; }
|
200 |
+
.home-link { background-color: #64748b; margin-top: 3rem; display: inline-block; width: auto; padding: 0.75rem 2rem; }
|
201 |
</style>
|
202 |
</head>
|
203 |
<body>
|
204 |
<div class="container">
|
205 |
+
<div class="header">
|
206 |
+
<h1>Results</h1>
|
207 |
+
<p>Your image has been processed. You can download the scrambled assets or view the unscrambled version below.</p>
|
208 |
+
</div>
|
209 |
<div class="grid">
|
210 |
<div class="card">
|
211 |
<h2>Unscrambled Preview (Protected)</h2>
|
212 |
+
<div class="image-container canvas-container">
|
|
|
213 |
<canvas id="unscrambled-canvas" width="{{ width }}" height="{{ height }}"></canvas>
|
214 |
</div>
|
215 |
+
<p>This is rendered on a canvas and is not a downloadable file. Right-clicking is disabled.</p>
|
216 |
</div>
|
217 |
<div class="card">
|
218 |
+
<h2>Scrambled Image</h2>
|
219 |
+
<div class="image-container">
|
|
|
220 |
<img src="{{ scrambled_image_url }}" alt="Scrambled Image">
|
221 |
+
</div>
|
222 |
+
<p>This is the shuffled version of your image. It can be unscrambled with the map file.</p>
|
223 |
+
<a href="{{ scrambled_image_url }}" download class="download-button">Download Scrambled PNG</a>
|
224 |
</div>
|
225 |
<div class="card">
|
226 |
+
<h2>Scrambling Map File</h2>
|
227 |
+
<div class="image-container">
|
228 |
+
<img src="{{ map_image_url }}" alt="Scrambling Map Visualization">
|
229 |
+
</div>
|
230 |
+
<p>This file contains the data needed to reconstruct the original image from the scrambled one.</p>
|
231 |
+
<a href="{{ map_json_url }}" download class="download-button">Download Map JSON</a>
|
232 |
</div>
|
233 |
</div>
|
234 |
+
<div style="text-align: center;">
|
235 |
+
<a href="/" class="home-link">Scramble Another Image</a>
|
236 |
+
</div>
|
237 |
</div>
|
238 |
|
239 |
<script>
|
|
|
240 |
const canvas = document.getElementById('unscrambled-canvas');
|
241 |
const ctx = canvas.getContext('2d');
|
242 |
const img = new Image();
|
|
|
|
|
243 |
canvas.oncontextmenu = (e) => { e.preventDefault(); return false; };
|
|
|
|
|
244 |
img.src = "data:image/png;base64,{{ unscrambled_base64 }}";
|
245 |
+
img.onload = () => ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
246 |
</script>
|
247 |
</body>
|
248 |
</html>
|
|
|
253 |
@app.route('/')
|
254 |
def home():
|
255 |
"""Renders the main upload page."""
|
256 |
+
return render_template_string(HOME_PAGE_TEMPLATE, random_seed=random.randint(10000, 99999))
|
257 |
|
258 |
@app.route('/process', methods=['POST'])
|
259 |
def process_image():
|
260 |
"""Handles the image processing."""
|
261 |
+
if 'image_file' not in request.files or request.files['image_file'].filename == '':
|
262 |
+
return render_template_string(HOME_PAGE_TEMPLATE, error="No image file selected. Please upload an image.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
263 |
|
264 |
+
file = request.files['image_file']
|
265 |
+
try:
|
266 |
+
grid_size = int(request.form.get('grid_size', 16))
|
267 |
+
seed = int(request.form.get('seed', 12345))
|
268 |
+
|
269 |
+
input_image = Image.open(file.stream).convert("RGB")
|
270 |
+
|
271 |
+
# 1. Scramble
|
272 |
+
scrambled_img, scramble_map = scramble_image(input_image, grid_size, seed)
|
273 |
+
if scrambled_img is None:
|
274 |
+
return render_template_string(HOME_PAGE_TEMPLATE, error=f"Image is too small for a {grid_size}x{grid_size} grid. Try a larger image or smaller grid.")
|
275 |
|
276 |
+
# 2. Unscramble in memory for preview
|
277 |
+
unscrambled_img = unscramble_image(scrambled_img, scramble_map, grid_size)
|
278 |
+
unscrambled_base64 = pil_to_base64(unscrambled_img)
|
279 |
+
|
280 |
+
# 3. Create visualization map image
|
281 |
+
map_viz_img = create_mapping_visualization(scramble_map, grid_size)
|
282 |
|
283 |
+
# 4. Prepare and save downloadable files
|
284 |
+
unique_id = uuid.uuid4()
|
285 |
+
|
286 |
+
# Scrambled PNG
|
287 |
+
scrambled_filename = f"{unique_id}_scrambled.png"
|
288 |
+
scrambled_img.save(os.path.join(app.config['UPLOAD_FOLDER'], scrambled_filename))
|
289 |
+
|
290 |
+
# Map Visualization PNG
|
291 |
+
map_viz_filename = f"{unique_id}_map_viz.png"
|
292 |
+
map_viz_img.save(os.path.join(app.config['UPLOAD_FOLDER'], map_viz_filename))
|
293 |
|
294 |
+
# NEW: Create and save the JSON map file
|
295 |
+
map_json_filename = f"{unique_id}_map.json"
|
296 |
+
map_data = {
|
297 |
+
"gridSize": grid_size,
|
298 |
+
"seed": seed,
|
299 |
+
"width": scrambled_img.width,
|
300 |
+
"height": scrambled_img.height,
|
301 |
+
"scrambleMap": scramble_map.tolist() # Convert numpy array to list for JSON
|
302 |
+
}
|
303 |
+
with open(os.path.join(app.config['UPLOAD_FOLDER'], map_json_filename), 'w') as f:
|
304 |
+
json.dump(map_data, f, indent=2)
|
305 |
|
306 |
+
# 5. Render the results page with links to all files
|
307 |
+
return render_template_string(
|
308 |
+
RESULTS_PAGE_TEMPLATE,
|
309 |
+
scrambled_image_url=url_for('get_file', filename=scrambled_filename),
|
310 |
+
map_image_url=url_for('get_file', filename=map_viz_filename),
|
311 |
+
map_json_url=url_for('get_file', filename=map_json_filename), # New URL for JSON
|
312 |
+
unscrambled_base64=unscrambled_base64,
|
313 |
+
width=unscrambled_img.width,
|
314 |
+
height=unscrambled_img.height
|
315 |
+
)
|
316 |
|
317 |
+
except Exception as e:
|
318 |
+
return render_template_string(HOME_PAGE_TEMPLATE, error=f"An error occurred: {e}")
|
319 |
|
320 |
+
@app.route('/temp_files/<filename>')
|
321 |
+
def get_file(filename):
|
322 |
+
"""Serves the generated files from the temporary directory."""
|
323 |
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
324 |
|
325 |
if __name__ == '__main__':
|