HedronCreeper commited on
Commit
d796e5c
·
verified ·
1 Parent(s): 9143b24

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -41
app.py CHANGED
@@ -4,21 +4,20 @@ import io, base64, struct
4
 
5
  app = Flask(__name__, static_folder="static")
6
 
7
- WIDTH, HEIGHT = 1184, 1184
8
  BG_COLOR = (88, 28, 135)
9
  TEXT_COLOR = (255, 255, 255)
10
  BITS_PER_CHANNEL = 2
11
  MASK = (1 << BITS_PER_CHANNEL) - 1
12
- CHANNELS = 3
13
- BITS_PER_PIXEL = BITS_PER_CHANNEL * CHANNELS
14
 
15
  def make_base_image():
16
  img = Image.new("RGB", (WIDTH, HEIGHT), BG_COLOR)
17
  draw = ImageDraw.Draw(img)
18
  try:
19
- font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 80)
20
  except:
21
- font = ImageFont.load_default()
22
  text = "CC"
23
  bbox = draw.textbbox((0, 0), text, font=font)
24
  tw = bbox[2] - bbox[0]
@@ -28,22 +27,12 @@ def make_base_image():
28
  draw.text((x, y), text, fill=TEXT_COLOR, font=font)
29
  return img
30
 
31
- def get_text_pixel_set():
32
- img = make_base_image()
33
- pixels = img.load()
34
- text_pixels = set()
35
- for y in range(HEIGHT):
36
- for x in range(WIDTH):
37
- if pixels[x, y] != BG_COLOR:
38
- text_pixels.add((x, y))
39
- return text_pixels
40
-
41
- def get_carrier_pixels():
42
- text_pixels = get_text_pixel_set()
43
  carriers = []
44
  for y in range(HEIGHT):
45
  for x in range(WIDTH):
46
- if (x, y) not in text_pixels:
47
  carriers.append((x, y))
48
  return carriers
49
 
@@ -66,17 +55,15 @@ def bytes_from_bits(bits):
66
  result.append(byte)
67
  return bytes(result)
68
 
69
- def encode(text):
70
- img = make_base_image()
71
- carriers = get_carrier_pixels()
72
- payload = text.encode("utf-8")
73
  header = struct.pack(">I", len(payload))
74
  data = header + payload
75
  bits = bits_from_bytes(data)
76
- max_bits = len(carriers) * BITS_PER_PIXEL
77
- if len(bits) > max_bits:
78
- raise ValueError(f"Text too long. Max ~{(max_bits // 8) - 4} bytes.")
79
- pixels = img.load()
80
  bit_idx = 0
81
  for (x, y) in carriers:
82
  if bit_idx >= len(bits):
@@ -93,10 +80,11 @@ def encode(text):
93
  chunk = chunk << 1
94
  new_channels.append((c & ~MASK) | chunk)
95
  pixels[x, y] = tuple(new_channels)
96
- return img
97
 
98
- def decode(img):
99
- carriers = get_carrier_pixels()
 
100
  pixels = img.load()
101
  bits = []
102
  for (x, y) in carriers:
@@ -104,25 +92,43 @@ def decode(img):
104
  for c in [r, g, b]:
105
  for i in range(BITS_PER_CHANNEL - 1, -1, -1):
106
  bits.append((c >> i) & 1)
107
- header_bits = bits[:32]
108
- length = struct.unpack(">I", bytes_from_bits(header_bits))[0]
109
- if length == 0 or length > 1_100_000:
110
- return None
111
- payload_bits = bits[32:32 + length * 8]
112
- return bytes_from_bits(payload_bits).decode("utf-8", errors="replace")
113
 
114
  @app.route("/")
115
  def index():
116
  return send_from_directory("static", "index.html")
117
 
118
- @app.route("/encode", methods=["POST"])
119
- def encode_route():
120
  data = request.json
121
  text = data.get("text", "")
122
  if not text:
123
  return jsonify({"error": "No text provided"}), 400
124
  try:
125
- img = encode(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  buf = io.BytesIO()
127
  img.save(buf, format="PNG")
128
  img_b64 = base64.b64encode(buf.getvalue()).decode()
@@ -136,10 +142,18 @@ def decode_route():
136
  return jsonify({"error": "No file uploaded"}), 400
137
  f = request.files["file"]
138
  img = Image.open(f).convert("RGB")
139
- text = decode(img)
140
- if text is None:
 
 
141
  return jsonify({"error": "Nothing encoded or invalid image"}), 400
142
- return jsonify({"text": text})
 
 
 
 
 
 
143
 
144
  if __name__ == "__main__":
145
  app.run(host="0.0.0.0", port=7860)
 
4
 
5
  app = Flask(__name__, static_folder="static")
6
 
7
+ WIDTH, HEIGHT = 1672, 1672
8
  BG_COLOR = (88, 28, 135)
9
  TEXT_COLOR = (255, 255, 255)
10
  BITS_PER_CHANNEL = 2
11
  MASK = (1 << BITS_PER_CHANNEL) - 1
12
+ MAX_BYTES = 2 * 1024 * 1024
 
13
 
14
  def make_base_image():
15
  img = Image.new("RGB", (WIDTH, HEIGHT), BG_COLOR)
16
  draw = ImageDraw.Draw(img)
17
  try:
18
+ font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 200)
19
  except:
20
+ font = ImageFont.load_default(size=200)
21
  text = "CC"
22
  bbox = draw.textbbox((0, 0), text, font=font)
23
  tw = bbox[2] - bbox[0]
 
27
  draw.text((x, y), text, fill=TEXT_COLOR, font=font)
28
  return img
29
 
30
+ def get_carriers(base_img):
31
+ pixels = base_img.load()
 
 
 
 
 
 
 
 
 
 
32
  carriers = []
33
  for y in range(HEIGHT):
34
  for x in range(WIDTH):
35
+ if pixels[x, y] == BG_COLOR:
36
  carriers.append((x, y))
37
  return carriers
38
 
 
55
  result.append(byte)
56
  return bytes(result)
57
 
58
+ def embed(payload):
59
+ base_img = make_base_image()
60
+ carriers = get_carriers(base_img)
 
61
  header = struct.pack(">I", len(payload))
62
  data = header + payload
63
  bits = bits_from_bytes(data)
64
+ if len(bits) > len(carriers) * BITS_PER_CHANNEL * 3:
65
+ raise ValueError(f"Data too large. Max {MAX_BYTES} bytes.")
66
+ pixels = base_img.load()
 
67
  bit_idx = 0
68
  for (x, y) in carriers:
69
  if bit_idx >= len(bits):
 
80
  chunk = chunk << 1
81
  new_channels.append((c & ~MASK) | chunk)
82
  pixels[x, y] = tuple(new_channels)
83
+ return base_img
84
 
85
+ def extract(img):
86
+ base_img = make_base_image()
87
+ carriers = get_carriers(base_img)
88
  pixels = img.load()
89
  bits = []
90
  for (x, y) in carriers:
 
92
  for c in [r, g, b]:
93
  for i in range(BITS_PER_CHANNEL - 1, -1, -1):
94
  bits.append((c >> i) & 1)
95
+ length = struct.unpack(">I", bytes_from_bits(bits[:32]))[0]
96
+ if length == 0 or length > MAX_BYTES + 100:
97
+ return None, None
98
+ payload = bytes_from_bits(bits[32:32 + length * 8])
99
+ return length, payload
 
100
 
101
  @app.route("/")
102
  def index():
103
  return send_from_directory("static", "index.html")
104
 
105
+ @app.route("/encode/text", methods=["POST"])
106
+ def encode_text():
107
  data = request.json
108
  text = data.get("text", "")
109
  if not text:
110
  return jsonify({"error": "No text provided"}), 400
111
  try:
112
+ payload = b'\x00' + text.encode("utf-8")
113
+ img = embed(payload)
114
+ buf = io.BytesIO()
115
+ img.save(buf, format="PNG")
116
+ img_b64 = base64.b64encode(buf.getvalue()).decode()
117
+ return jsonify({"image": img_b64})
118
+ except Exception as e:
119
+ return jsonify({"error": str(e)}), 500
120
+
121
+ @app.route("/encode/image", methods=["POST"])
122
+ def encode_image():
123
+ if "file" not in request.files:
124
+ return jsonify({"error": "No file uploaded"}), 400
125
+ f = request.files["file"]
126
+ raw = f.read()
127
+ if len(raw) > MAX_BYTES:
128
+ return jsonify({"error": f"Image too large. Max 2MB, got {len(raw) / 1024 / 1024:.2f}MB"}), 400
129
+ try:
130
+ payload = b'\x01' + raw
131
+ img = embed(payload)
132
  buf = io.BytesIO()
133
  img.save(buf, format="PNG")
134
  img_b64 = base64.b64encode(buf.getvalue()).decode()
 
142
  return jsonify({"error": "No file uploaded"}), 400
143
  f = request.files["file"]
144
  img = Image.open(f).convert("RGB")
145
+ if img.size != (WIDTH, HEIGHT):
146
+ return jsonify({"error": "Not a valid ChromaCode image"}), 400
147
+ length, payload = extract(img)
148
+ if payload is None:
149
  return jsonify({"error": "Nothing encoded or invalid image"}), 400
150
+ mode = payload[0]
151
+ content = payload[1:]
152
+ if mode == 0:
153
+ return jsonify({"type": "text", "text": content.decode("utf-8", errors="replace")})
154
+ else:
155
+ img_b64 = base64.b64encode(content).decode()
156
+ return jsonify({"type": "image", "image": img_b64})
157
 
158
  if __name__ == "__main__":
159
  app.run(host="0.0.0.0", port=7860)