Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -6,6 +6,7 @@ import io
|
|
6 |
import random
|
7 |
import os
|
8 |
import uuid
|
|
|
9 |
|
10 |
# --- Configuration & Setup ---
|
11 |
# Create a directory to store temporary output files
|
@@ -17,18 +18,15 @@ os.makedirs(TEMP_DIR, exist_ok=True)
|
|
17 |
# ... (All the functions like scramble_image, unscramble_image, etc., are the same)
|
18 |
|
19 |
def process_image_for_grid(image_pil, grid_size):
|
20 |
-
"""Crops the image to be perfectly divisible by the grid size."""
|
21 |
if image_pil is None: return None, 0, 0
|
22 |
img_width, img_height = image_pil.size
|
23 |
-
tile_w = img_width // grid_size
|
24 |
-
tile_h = img_height // grid_size
|
25 |
if tile_w == 0 or tile_h == 0:
|
26 |
-
raise gr.Error(f"Image is too small for a {grid_size}x{grid_size} grid.
|
27 |
cropped_image = image_pil.crop((0, 0, tile_w * grid_size, tile_h * grid_size))
|
28 |
return cropped_image, tile_w, tile_h
|
29 |
|
30 |
def scramble_image(image_pil, grid_size, seed):
|
31 |
-
"""Scrambles an image and returns the PIL image and the map."""
|
32 |
cropped_image, tile_w, tile_h = process_image_for_grid(image_pil, grid_size)
|
33 |
tiles = [cropped_image.crop((j * tile_w, i * tile_h, (j + 1) * tile_w, (i + 1) * tile_h)) for i in range(grid_size) for j in range(grid_size)]
|
34 |
rng = np.random.default_rng(seed=int(seed))
|
@@ -40,7 +38,6 @@ def scramble_image(image_pil, grid_size, seed):
|
|
40 |
return scrambled_image, scramble_map
|
41 |
|
42 |
def unscramble_image(scrambled_pil, scramble_map, grid_size):
|
43 |
-
"""Unscrambles an image using the provided scramble_map."""
|
44 |
cropped_image, tile_w, tile_h = process_image_for_grid(scrambled_pil, grid_size)
|
45 |
scrambled_tiles = [cropped_image.crop((j * tile_w, i * tile_h, (j + 1) * tile_w, (i + 1) * tile_h)) for i in range(grid_size) for j in range(grid_size)]
|
46 |
unscrambled_image = Image.new('RGB', cropped_image.size)
|
@@ -50,7 +47,6 @@ def unscramble_image(scrambled_pil, scramble_map, grid_size):
|
|
50 |
return unscrambled_image
|
51 |
|
52 |
def create_mapping_visualization(scramble_map, grid_size):
|
53 |
-
"""Creates a PIL image that visualizes the scrambling map."""
|
54 |
map_size=(512, 512)
|
55 |
vis_image = Image.new('RGB', map_size, color='lightgray')
|
56 |
draw = ImageDraw.Draw(vis_image)
|
@@ -68,13 +64,11 @@ def create_mapping_visualization(scramble_map, grid_size):
|
|
68 |
return vis_image
|
69 |
|
70 |
def pil_to_base64(pil_image):
|
71 |
-
"""Converts a PIL Image to a Base64 string for HTML embedding."""
|
72 |
buffered = io.BytesIO()
|
73 |
pil_image.save(buffered, format="PNG")
|
74 |
return base64.b64encode(buffered.getvalue()).decode("utf-8")
|
75 |
|
76 |
def create_canvas_html(base64_string, width, height):
|
77 |
-
"""Creates the HTML and JavaScript to render an image on a canvas."""
|
78 |
return f"""
|
79 |
<div style="display: flex; justify-content: center; align-items: center; background: #f0f0f0;">
|
80 |
<canvas id="unscrambled-canvas" width="{width}" height="{height}" style="max-width: 100%; max-height: 512px; object-fit: contain;"></canvas>
|
@@ -97,10 +91,11 @@ def create_canvas_html(base64_string, width, height):
|
|
97 |
|
98 |
def process_and_display(input_image, grid_size, seed):
|
99 |
"""
|
100 |
-
Main orchestrator function.
|
101 |
"""
|
102 |
if input_image is None:
|
103 |
-
|
|
|
104 |
|
105 |
# 1. Scramble the image
|
106 |
scrambled_img, scramble_map = scramble_image(input_image, grid_size, seed)
|
@@ -113,20 +108,31 @@ def process_and_display(input_image, grid_size, seed):
|
|
113 |
# 3. Create map visualization
|
114 |
map_viz_img = create_mapping_visualization(scramble_map, grid_size)
|
115 |
|
116 |
-
# ---
|
117 |
unique_id = uuid.uuid4()
|
118 |
|
119 |
-
# Save the scrambled image
|
120 |
scrambled_filepath = os.path.join(TEMP_DIR, f"{unique_id}_scrambled.png")
|
121 |
scrambled_img.save(scrambled_filepath)
|
122 |
|
123 |
-
# Save the map visualization
|
124 |
map_viz_filepath = os.path.join(TEMP_DIR, f"{unique_id}_map.png")
|
125 |
map_viz_img.save(map_viz_filepath)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
|
127 |
-
# 4. Return
|
128 |
-
|
129 |
-
return scrambled_filepath, canvas_html, map_viz_filepath, scrambled_filepath
|
130 |
|
131 |
|
132 |
# --- Gradio UI Definition ---
|
@@ -134,9 +140,9 @@ def process_and_display(input_image, grid_size, seed):
|
|
134 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
135 |
gr.Markdown(
|
136 |
"""
|
137 |
-
# 🖼️ Secure Image Scrambler & Viewer (
|
138 |
-
|
139 |
-
|
140 |
"""
|
141 |
)
|
142 |
|
@@ -149,48 +155,53 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
149 |
)
|
150 |
|
151 |
with gr.Accordion("Settings", open=True):
|
152 |
-
grid_size_slider = gr.Slider(
|
153 |
-
|
154 |
-
)
|
155 |
-
seed_input = gr.Number(
|
156 |
-
value=lambda: random.randint(0, 99999), label="Scramble Seed"
|
157 |
-
)
|
158 |
|
159 |
submit_btn = gr.Button("Scramble & Process", variant="primary")
|
160 |
|
161 |
with gr.Column(scale=2):
|
162 |
with gr.Tabs():
|
163 |
-
with gr.TabItem("
|
|
|
164 |
scrambled_output = gr.Image(
|
165 |
label="Scrambled Image Preview",
|
166 |
-
type="filepath",
|
167 |
interactive=False,
|
168 |
)
|
169 |
-
|
170 |
-
|
171 |
-
|
|
|
|
|
|
|
|
|
|
|
172 |
interactive=False
|
173 |
)
|
|
|
174 |
with gr.TabItem("Unscrambled Preview (Protected)"):
|
175 |
unscrambled_canvas = gr.HTML(
|
176 |
label="Unscrambled Preview (Not directly downloadable)"
|
177 |
)
|
178 |
-
|
|
|
179 |
mapping_output = gr.Image(
|
180 |
label="Mapping Key Visualization",
|
181 |
-
type="filepath",
|
182 |
interactive=False
|
183 |
)
|
184 |
|
185 |
-
# Connect the button to the main function, mapping outputs correctly
|
186 |
submit_btn.click(
|
187 |
fn=process_and_display,
|
188 |
inputs=[input_image, grid_size_slider, seed_input],
|
189 |
outputs=[
|
190 |
-
scrambled_output, # Receives scrambled_filepath
|
191 |
unscrambled_canvas, # Receives canvas_html
|
192 |
mapping_output, # Receives map_viz_filepath
|
193 |
-
|
|
|
194 |
]
|
195 |
)
|
196 |
|
|
|
6 |
import random
|
7 |
import os
|
8 |
import uuid
|
9 |
+
import json # <-- Import the json library
|
10 |
|
11 |
# --- Configuration & Setup ---
|
12 |
# Create a directory to store temporary output files
|
|
|
18 |
# ... (All the functions like scramble_image, unscramble_image, etc., are the same)
|
19 |
|
20 |
def process_image_for_grid(image_pil, grid_size):
|
|
|
21 |
if image_pil is None: return None, 0, 0
|
22 |
img_width, img_height = image_pil.size
|
23 |
+
tile_w, tile_h = img_width // grid_size, img_height // grid_size
|
|
|
24 |
if tile_w == 0 or tile_h == 0:
|
25 |
+
raise gr.Error(f"Image is too small for a {grid_size}x{grid_size} grid.")
|
26 |
cropped_image = image_pil.crop((0, 0, tile_w * grid_size, tile_h * grid_size))
|
27 |
return cropped_image, tile_w, tile_h
|
28 |
|
29 |
def scramble_image(image_pil, grid_size, seed):
|
|
|
30 |
cropped_image, tile_w, tile_h = process_image_for_grid(image_pil, grid_size)
|
31 |
tiles = [cropped_image.crop((j * tile_w, i * tile_h, (j + 1) * tile_w, (i + 1) * tile_h)) for i in range(grid_size) for j in range(grid_size)]
|
32 |
rng = np.random.default_rng(seed=int(seed))
|
|
|
38 |
return scrambled_image, scramble_map
|
39 |
|
40 |
def unscramble_image(scrambled_pil, scramble_map, grid_size):
|
|
|
41 |
cropped_image, tile_w, tile_h = process_image_for_grid(scrambled_pil, grid_size)
|
42 |
scrambled_tiles = [cropped_image.crop((j * tile_w, i * tile_h, (j + 1) * tile_w, (i + 1) * tile_h)) for i in range(grid_size) for j in range(grid_size)]
|
43 |
unscrambled_image = Image.new('RGB', cropped_image.size)
|
|
|
47 |
return unscrambled_image
|
48 |
|
49 |
def create_mapping_visualization(scramble_map, grid_size):
|
|
|
50 |
map_size=(512, 512)
|
51 |
vis_image = Image.new('RGB', map_size, color='lightgray')
|
52 |
draw = ImageDraw.Draw(vis_image)
|
|
|
64 |
return vis_image
|
65 |
|
66 |
def pil_to_base64(pil_image):
|
|
|
67 |
buffered = io.BytesIO()
|
68 |
pil_image.save(buffered, format="PNG")
|
69 |
return base64.b64encode(buffered.getvalue()).decode("utf-8")
|
70 |
|
71 |
def create_canvas_html(base64_string, width, height):
|
|
|
72 |
return f"""
|
73 |
<div style="display: flex; justify-content: center; align-items: center; background: #f0f0f0;">
|
74 |
<canvas id="unscrambled-canvas" width="{width}" height="{height}" style="max-width: 100%; max-height: 512px; object-fit: contain;"></canvas>
|
|
|
91 |
|
92 |
def process_and_display(input_image, grid_size, seed):
|
93 |
"""
|
94 |
+
Main orchestrator function. Saves PNGs and JSON and returns all file paths.
|
95 |
"""
|
96 |
if input_image is None:
|
97 |
+
# Return empty placeholders for all outputs
|
98 |
+
return None, "<div>Please upload an image to begin.</div>", None, None, None
|
99 |
|
100 |
# 1. Scramble the image
|
101 |
scrambled_img, scramble_map = scramble_image(input_image, grid_size, seed)
|
|
|
108 |
# 3. Create map visualization
|
109 |
map_viz_img = create_mapping_visualization(scramble_map, grid_size)
|
110 |
|
111 |
+
# --- Save all files and get their paths ---
|
112 |
unique_id = uuid.uuid4()
|
113 |
|
114 |
+
# Save the scrambled image PNG
|
115 |
scrambled_filepath = os.path.join(TEMP_DIR, f"{unique_id}_scrambled.png")
|
116 |
scrambled_img.save(scrambled_filepath)
|
117 |
|
118 |
+
# Save the map visualization PNG
|
119 |
map_viz_filepath = os.path.join(TEMP_DIR, f"{unique_id}_map.png")
|
120 |
map_viz_img.save(map_viz_filepath)
|
121 |
+
|
122 |
+
# NEW: Create and save the JSON map file
|
123 |
+
map_json_filepath = os.path.join(TEMP_DIR, f"{unique_id}_map.json")
|
124 |
+
map_data = {
|
125 |
+
"gridSize": grid_size,
|
126 |
+
"seed": seed,
|
127 |
+
"width": scrambled_img.width,
|
128 |
+
"height": scrambled_img.height,
|
129 |
+
"scrambleMap": scramble_map.tolist() # Convert numpy array for JSON
|
130 |
+
}
|
131 |
+
with open(map_json_filepath, 'w') as f:
|
132 |
+
json.dump(map_data, f, indent=2)
|
133 |
|
134 |
+
# 4. Return all necessary file paths and the canvas HTML
|
135 |
+
return scrambled_filepath, canvas_html, map_viz_filepath, scrambled_filepath, map_json_filepath
|
|
|
136 |
|
137 |
|
138 |
# --- Gradio UI Definition ---
|
|
|
140 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
141 |
gr.Markdown(
|
142 |
"""
|
143 |
+
# 🖼️ Secure Image Scrambler & Viewer (v3)
|
144 |
+
Upload an image to create a scrambled `.png` file and a `.json` map file.
|
145 |
+
The unscrambled original can be viewed in a protected preview window that prevents easy downloading.
|
146 |
"""
|
147 |
)
|
148 |
|
|
|
155 |
)
|
156 |
|
157 |
with gr.Accordion("Settings", open=True):
|
158 |
+
grid_size_slider = gr.Slider(minimum=2, maximum=32, value=8, step=1, label="Grid Size (NxN)")
|
159 |
+
seed_input = gr.Number(value=lambda: random.randint(0, 99999), label="Scramble Seed")
|
|
|
|
|
|
|
|
|
160 |
|
161 |
submit_btn = gr.Button("Scramble & Process", variant="primary")
|
162 |
|
163 |
with gr.Column(scale=2):
|
164 |
with gr.Tabs():
|
165 |
+
with gr.TabItem("Downloads"):
|
166 |
+
gr.Markdown("### Scrambled Image")
|
167 |
scrambled_output = gr.Image(
|
168 |
label="Scrambled Image Preview",
|
169 |
+
type="filepath",
|
170 |
interactive=False,
|
171 |
)
|
172 |
+
downloadable_png = gr.File(
|
173 |
+
label="Download Scrambled PNG File",
|
174 |
+
interactive=False
|
175 |
+
)
|
176 |
+
gr.Markdown("---")
|
177 |
+
gr.Markdown("### Scrambling Map")
|
178 |
+
downloadable_json = gr.File(
|
179 |
+
label="Download Map JSON File",
|
180 |
interactive=False
|
181 |
)
|
182 |
+
|
183 |
with gr.TabItem("Unscrambled Preview (Protected)"):
|
184 |
unscrambled_canvas = gr.HTML(
|
185 |
label="Unscrambled Preview (Not directly downloadable)"
|
186 |
)
|
187 |
+
|
188 |
+
with gr.TabItem("Map Visualization"):
|
189 |
mapping_output = gr.Image(
|
190 |
label="Mapping Key Visualization",
|
191 |
+
type="filepath",
|
192 |
interactive=False
|
193 |
)
|
194 |
|
195 |
+
# Connect the button to the main function, mapping all outputs correctly
|
196 |
submit_btn.click(
|
197 |
fn=process_and_display,
|
198 |
inputs=[input_image, grid_size_slider, seed_input],
|
199 |
outputs=[
|
200 |
+
scrambled_output, # Receives scrambled_filepath for display
|
201 |
unscrambled_canvas, # Receives canvas_html
|
202 |
mapping_output, # Receives map_viz_filepath
|
203 |
+
downloadable_png, # Receives scrambled_filepath for download
|
204 |
+
downloadable_json # Receives map_json_filepath for download
|
205 |
]
|
206 |
)
|
207 |
|