HedronCreeper commited on
Commit
60eab6c
·
verified ·
1 Parent(s): 230e407

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -10
app.py CHANGED
@@ -34,31 +34,44 @@ def get_font_for_height(target_px):
34
  def make_base_image(w, h):
35
  img = Image.new("RGB", (w, h), BG_COLOR)
36
  draw = ImageDraw.Draw(img)
37
- target_h = int(min(w, h) * 2.0)
38
- target_h = min(target_h, min(w, h) - 4)
 
 
39
  font = get_font_for_height(target_h)
 
40
  bbox = draw.textbbox((0, 0), "CC", font=font)
41
  tw = bbox[2] - bbox[0]
42
  th = bbox[3] - bbox[1]
 
43
  x = (w - tw) // 2 - bbox[0]
44
  y = (h - th) // 2 - bbox[1]
 
45
  draw.text((x, y), "CC", fill=TEXT_COLOR, font=font)
 
46
  return img
47
 
48
  def calc_size(payload_bytes):
49
  total_bits = (len(payload_bytes) + 4) * 8
 
 
50
  pixels_needed = math.ceil(total_bits / 6)
51
- side = max(MIN_SIZE, math.ceil(math.sqrt(pixels_needed * 4.5)))
 
 
 
52
  return side, side
53
 
54
  def get_carriers(w, h):
55
  base = make_base_image(w, h)
56
  pixels = base.load()
57
  carriers = []
 
58
  for y in range(h):
59
  for x in range(w):
60
  if pixels[x, y] == BG_COLOR:
61
  carriers.append((x, y))
 
62
  return carriers, base
63
 
64
  def bits_from_bytes(data):
@@ -82,24 +95,33 @@ def bytes_from_bits(bits):
82
 
83
  def embed(payload):
84
  w, h = calc_size(payload)
 
85
  for _ in range(10):
86
  carriers, img = get_carriers(w, h)
 
87
  header = struct.pack(">I", len(payload))
88
  data = header + payload
89
  bits = bits_from_bytes(data)
 
90
  if len(bits) <= len(carriers) * BITS_PER_CHANNEL * 3:
91
  break
92
- w = math.ceil(w * 1.2)
93
- h = math.ceil(h * 1.2)
 
 
94
  else:
95
  raise ValueError("Data too large even after resizing.")
 
96
  pixels = img.load()
97
  bit_idx = 0
 
98
  for (x, y) in carriers:
99
  if bit_idx >= len(bits):
100
  break
 
101
  r, g, b = pixels[x, y]
102
  new_channels = []
 
103
  for c in [r, g, b]:
104
  chunk = 0
105
  for _ in range(BITS_PER_CHANNEL):
@@ -107,25 +129,33 @@ def embed(payload):
107
  chunk = (chunk << 1) | bits[bit_idx]
108
  bit_idx += 1
109
  else:
110
- chunk = chunk << 1
111
  new_channels.append((c & ~MASK) | chunk)
 
112
  pixels[x, y] = tuple(new_channels)
 
113
  return img
114
 
115
  def extract(img):
116
  w, h = img.size
117
  carriers, _ = get_carriers(w, h)
 
118
  pixels = img.load()
119
  bits = []
 
120
  for (x, y) in carriers:
121
  r, g, b = pixels[x, y]
122
  for c in [r, g, b]:
123
  for i in range(BITS_PER_CHANNEL - 1, -1, -1):
124
  bits.append((c >> i) & 1)
 
125
  length = struct.unpack(">I", bytes_from_bits(bits[:32]))[0]
 
126
  if length == 0 or length > MAX_BYTES + 10:
127
  return None, None
 
128
  payload = bytes_from_bits(bits[32:32 + length * 8])
 
129
  return length, payload
130
 
131
  @app.route("/")
@@ -136,17 +166,23 @@ def index():
136
  def encode_text():
137
  data = request.json
138
  text = data.get("text", "")
 
139
  if not text:
140
  return jsonify({"error": "No text provided"}), 400
 
141
  payload = b'\x00' + text.encode("utf-8")
 
142
  if len(payload) > MAX_BYTES:
143
  return jsonify({"error": "Text too large. Max 5MB."}), 400
 
144
  try:
145
  img = embed(payload)
146
  buf = io.BytesIO()
147
  img.save(buf, format="PNG")
 
148
  img_b64 = base64.b64encode(buf.getvalue()).decode()
149
  w, h = img.size
 
150
  return jsonify({"image": img_b64, "size": f"{w}×{h}"})
151
  except Exception as e:
152
  return jsonify({"error": str(e)}), 500
@@ -155,19 +191,27 @@ def encode_text():
155
  def encode_file():
156
  if "file" not in request.files:
157
  return jsonify({"error": "No file uploaded"}), 400
 
158
  f = request.files["file"]
159
  raw = f.read()
 
160
  if len(raw) > MAX_BYTES:
161
- return jsonify({"error": f"File too large: {len(raw)/1024/1024:.2f}MB. Max is 5MB."}), 400
 
 
 
162
  fname = f.filename or ""
163
  mode = b'\x02' if fname.lower().endswith(".pdf") else b'\x01'
164
  payload = mode + raw
 
165
  try:
166
  img = embed(payload)
167
  buf = io.BytesIO()
168
  img.save(buf, format="PNG")
 
169
  img_b64 = base64.b64encode(buf.getvalue()).decode()
170
  w, h = img.size
 
171
  return jsonify({"image": img_b64, "size": f"{w}×{h}"})
172
  except Exception as e:
173
  return jsonify({"error": str(e)}), 500
@@ -176,19 +220,36 @@ def encode_file():
176
  def decode_route():
177
  if "file" not in request.files:
178
  return jsonify({"error": "No file uploaded"}), 400
 
179
  f = request.files["file"]
180
  img = Image.open(f).convert("RGB")
 
181
  length, payload = extract(img)
 
182
  if payload is None:
183
  return jsonify({"error": "Nothing encoded or invalid image"}), 400
 
184
  mode = payload[0]
185
  content = payload[1:]
 
186
  if mode == 0:
187
- return jsonify({"type": "text", "text": content.decode("utf-8", errors="replace")})
 
 
 
 
188
  elif mode == 1:
189
- return jsonify({"type": "image", "image": base64.b64encode(content).decode()})
 
 
 
 
190
  elif mode == 2:
191
- return jsonify({"type": "pdf", "pdf": base64.b64encode(content).decode()})
 
 
 
 
192
  return jsonify({"error": "Unknown mode"}), 400
193
 
194
  if __name__ == "__main__":
 
34
  def make_base_image(w, h):
35
  img = Image.new("RGB", (w, h), BG_COLOR)
36
  draw = ImageDraw.Draw(img)
37
+
38
+ # MASSIVE "CC" (~5x bigger visual dominance)
39
+ target_h = int(min(w, h) * 0.95)
40
+
41
  font = get_font_for_height(target_h)
42
+
43
  bbox = draw.textbbox((0, 0), "CC", font=font)
44
  tw = bbox[2] - bbox[0]
45
  th = bbox[3] - bbox[1]
46
+
47
  x = (w - tw) // 2 - bbox[0]
48
  y = (h - th) // 2 - bbox[1]
49
+
50
  draw.text((x, y), "CC", fill=TEXT_COLOR, font=font)
51
+
52
  return img
53
 
54
  def calc_size(payload_bytes):
55
  total_bits = (len(payload_bytes) + 4) * 8
56
+
57
+ # Each pixel = 6 bits (2 bits * RGB)
58
  pixels_needed = math.ceil(total_bits / 6)
59
+
60
+ # Increased multiplier due to large CC blocking space
61
+ side = max(MIN_SIZE, math.ceil(math.sqrt(pixels_needed * 10)))
62
+
63
  return side, side
64
 
65
  def get_carriers(w, h):
66
  base = make_base_image(w, h)
67
  pixels = base.load()
68
  carriers = []
69
+
70
  for y in range(h):
71
  for x in range(w):
72
  if pixels[x, y] == BG_COLOR:
73
  carriers.append((x, y))
74
+
75
  return carriers, base
76
 
77
  def bits_from_bytes(data):
 
95
 
96
  def embed(payload):
97
  w, h = calc_size(payload)
98
+
99
  for _ in range(10):
100
  carriers, img = get_carriers(w, h)
101
+
102
  header = struct.pack(">I", len(payload))
103
  data = header + payload
104
  bits = bits_from_bytes(data)
105
+
106
  if len(bits) <= len(carriers) * BITS_PER_CHANNEL * 3:
107
  break
108
+
109
+ # Grow faster because fewer carriers now
110
+ w = math.ceil(w * 1.3)
111
+ h = math.ceil(h * 1.3)
112
  else:
113
  raise ValueError("Data too large even after resizing.")
114
+
115
  pixels = img.load()
116
  bit_idx = 0
117
+
118
  for (x, y) in carriers:
119
  if bit_idx >= len(bits):
120
  break
121
+
122
  r, g, b = pixels[x, y]
123
  new_channels = []
124
+
125
  for c in [r, g, b]:
126
  chunk = 0
127
  for _ in range(BITS_PER_CHANNEL):
 
129
  chunk = (chunk << 1) | bits[bit_idx]
130
  bit_idx += 1
131
  else:
132
+ chunk <<= 1
133
  new_channels.append((c & ~MASK) | chunk)
134
+
135
  pixels[x, y] = tuple(new_channels)
136
+
137
  return img
138
 
139
  def extract(img):
140
  w, h = img.size
141
  carriers, _ = get_carriers(w, h)
142
+
143
  pixels = img.load()
144
  bits = []
145
+
146
  for (x, y) in carriers:
147
  r, g, b = pixels[x, y]
148
  for c in [r, g, b]:
149
  for i in range(BITS_PER_CHANNEL - 1, -1, -1):
150
  bits.append((c >> i) & 1)
151
+
152
  length = struct.unpack(">I", bytes_from_bits(bits[:32]))[0]
153
+
154
  if length == 0 or length > MAX_BYTES + 10:
155
  return None, None
156
+
157
  payload = bytes_from_bits(bits[32:32 + length * 8])
158
+
159
  return length, payload
160
 
161
  @app.route("/")
 
166
  def encode_text():
167
  data = request.json
168
  text = data.get("text", "")
169
+
170
  if not text:
171
  return jsonify({"error": "No text provided"}), 400
172
+
173
  payload = b'\x00' + text.encode("utf-8")
174
+
175
  if len(payload) > MAX_BYTES:
176
  return jsonify({"error": "Text too large. Max 5MB."}), 400
177
+
178
  try:
179
  img = embed(payload)
180
  buf = io.BytesIO()
181
  img.save(buf, format="PNG")
182
+
183
  img_b64 = base64.b64encode(buf.getvalue()).decode()
184
  w, h = img.size
185
+
186
  return jsonify({"image": img_b64, "size": f"{w}×{h}"})
187
  except Exception as e:
188
  return jsonify({"error": str(e)}), 500
 
191
  def encode_file():
192
  if "file" not in request.files:
193
  return jsonify({"error": "No file uploaded"}), 400
194
+
195
  f = request.files["file"]
196
  raw = f.read()
197
+
198
  if len(raw) > MAX_BYTES:
199
+ return jsonify({
200
+ "error": f"File too large: {len(raw)/1024/1024:.2f}MB. Max is 5MB."
201
+ }), 400
202
+
203
  fname = f.filename or ""
204
  mode = b'\x02' if fname.lower().endswith(".pdf") else b'\x01'
205
  payload = mode + raw
206
+
207
  try:
208
  img = embed(payload)
209
  buf = io.BytesIO()
210
  img.save(buf, format="PNG")
211
+
212
  img_b64 = base64.b64encode(buf.getvalue()).decode()
213
  w, h = img.size
214
+
215
  return jsonify({"image": img_b64, "size": f"{w}×{h}"})
216
  except Exception as e:
217
  return jsonify({"error": str(e)}), 500
 
220
  def decode_route():
221
  if "file" not in request.files:
222
  return jsonify({"error": "No file uploaded"}), 400
223
+
224
  f = request.files["file"]
225
  img = Image.open(f).convert("RGB")
226
+
227
  length, payload = extract(img)
228
+
229
  if payload is None:
230
  return jsonify({"error": "Nothing encoded or invalid image"}), 400
231
+
232
  mode = payload[0]
233
  content = payload[1:]
234
+
235
  if mode == 0:
236
+ return jsonify({
237
+ "type": "text",
238
+ "text": content.decode("utf-8", errors="replace")
239
+ })
240
+
241
  elif mode == 1:
242
+ return jsonify({
243
+ "type": "image",
244
+ "image": base64.b64encode(content).decode()
245
+ })
246
+
247
  elif mode == 2:
248
+ return jsonify({
249
+ "type": "pdf",
250
+ "pdf": base64.b64encode(content).decode()
251
+ })
252
+
253
  return jsonify({"error": "Unknown mode"}), 400
254
 
255
  if __name__ == "__main__":