broadfield-dev commited on
Commit
20c5dc9
·
verified ·
1 Parent(s): 0db618a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -36
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. Please use a larger image or a smaller grid size.")
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. Now saves files and returns paths.
101
  """
102
  if input_image is None:
103
- return None, "<div>Please upload an image to begin.</div>", None, None
 
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
- # --- NEW: Save files and return paths ---
117
  unique_id = uuid.uuid4()
118
 
119
- # Save the scrambled image as a PNG file
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 as a PNG file
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 the file paths and HTML to the Gradio components
128
- # The file path is returned twice: once for the gr.Image display and once for the gr.File download.
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 (v2)
138
- This tool scrambles an image so you can share it securely. The scrambled version is provided as a downloadable `.png` file.
139
- You can view the unscrambled original in a special preview window that prevents easy downloading.
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
- minimum=2, maximum=32, value=8, step=1, label="Grid Size (NxN)"
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("Scrambled Image"):
 
164
  scrambled_output = gr.Image(
165
  label="Scrambled Image Preview",
166
- type="filepath", # This component expects a file path
167
  interactive=False,
168
  )
169
- # NEW: File download component
170
- downloadable_file = gr.File(
171
- label="Download Scrambled PNG",
 
 
 
 
 
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
- with gr.TabItem("Scrambling Map"):
 
179
  mapping_output = gr.Image(
180
  label="Mapping Key Visualization",
181
- type="filepath", # This component also expects a file path
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
- downloadable_file # Receives scrambled_filepath
 
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