Spaces:
Sleeping
Sleeping
| from flask import Flask, request, jsonify, send_from_directory | |
| from PIL import Image, ImageDraw, ImageFont | |
| import io, base64, struct, math | |
| app = Flask(__name__, static_folder="static") | |
| BG_COLOR = (88, 28, 135) | |
| TEXT_COLOR = (255, 255, 255) | |
| BITS_PER_CHANNEL = 2 | |
| MASK = (1 << BITS_PER_CHANNEL) - 1 | |
| MAX_BYTES = 5 * 1024 * 1024 | |
| MIN_SIZE = 200 | |
| def get_font_for_height(target_px): | |
| font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" | |
| size = max(1, target_px) | |
| for _ in range(50): | |
| try: | |
| font = ImageFont.truetype(font_path, size) | |
| except: | |
| return ImageFont.load_default() | |
| dummy = Image.new("RGB", (1, 1)) | |
| draw = ImageDraw.Draw(dummy) | |
| bbox = draw.textbbox((0, 0), "CC", font=font) | |
| actual_h = bbox[3] - bbox[1] | |
| if abs(actual_h - target_px) <= 1: | |
| break | |
| if actual_h == 0: | |
| size += 1 | |
| else: | |
| size = max(1, int(size * target_px / actual_h)) | |
| return font | |
| def make_base_image(w, h): | |
| img = Image.new("RGB", (w, h), BG_COLOR) | |
| draw = ImageDraw.Draw(img) | |
| # Make the text 5 times bigger | |
| target_h = int(min(w, h) * 10.0) # Increased from 2.0 to 10.0 (5x larger) | |
| target_h = min(target_h, min(w, h) - 4) | |
| font = get_font_for_height(target_h) | |
| bbox = draw.textbbox((0, 0), "CC", font=font) | |
| tw = bbox[2] - bbox[0] | |
| th = bbox[3] - bbox[1] | |
| x = (w - tw) // 2 - bbox[0] | |
| y = (h - th) // 2 - bbox[1] | |
| draw.text((x, y), "CC", fill=TEXT_COLOR, font=font) | |
| return img | |
| def calc_size(payload_bytes): | |
| total_bits = (len(payload_bytes) + 4) * 8 | |
| pixels_needed = math.ceil(total_bits / 6) | |
| # Increase size calculation to accommodate larger text | |
| side = max(MIN_SIZE, math.ceil(math.sqrt(pixels_needed * 4.5 * 25))) # 25 = 5^2 (area scaling factor) | |
| return side, side | |
| def get_carriers(w, h): | |
| base = make_base_image(w, h) | |
| pixels = base.load() | |
| carriers = [] | |
| for y in range(h): | |
| for x in range(w): | |
| if pixels[x, y] == BG_COLOR: | |
| carriers.append((x, y)) | |
| return carriers, base | |
| def bits_from_bytes(data): | |
| bits = [] | |
| for byte in data: | |
| for i in range(7, -1, -1): | |
| bits.append((byte >> i) & 1) | |
| return bits | |
| def bytes_from_bits(bits): | |
| result = [] | |
| for i in range(0, len(bits), 8): | |
| chunk = bits[i:i+8] | |
| if len(chunk) < 8: | |
| break | |
| byte = 0 | |
| for b in chunk: | |
| byte = (byte << 1) | b | |
| result.append(byte) | |
| return bytes(result) | |
| def embed(payload): | |
| w, h = calc_size(payload) | |
| for _ in range(10): | |
| carriers, img = get_carriers(w, h) | |
| header = struct.pack(">I", len(payload)) | |
| data = header + payload | |
| bits = bits_from_bytes(data) | |
| if len(bits) <= len(carriers) * BITS_PER_CHANNEL * 3: | |
| break | |
| w = math.ceil(w * 1.2) | |
| h = math.ceil(h * 1.2) | |
| else: | |
| raise ValueError("Data too large even after resizing.") | |
| pixels = img.load() | |
| bit_idx = 0 | |
| for (x, y) in carriers: | |
| if bit_idx >= len(bits): | |
| break | |
| r, g, b = pixels[x, y] | |
| new_channels = [] | |
| for c in [r, g, b]: | |
| chunk = 0 | |
| for _ in range(BITS_PER_CHANNEL): | |
| if bit_idx < len(bits): | |
| chunk = (chunk << 1) | bits[bit_idx] | |
| bit_idx += 1 | |
| else: | |
| chunk = chunk << 1 | |
| new_channels.append((c & ~MASK) | chunk) | |
| pixels[x, y] = tuple(new_channels) | |
| return img | |
| def extract(img): | |
| w, h = img.size | |
| carriers, _ = get_carriers(w, h) | |
| pixels = img.load() | |
| bits = [] | |
| for (x, y) in carriers: | |
| r, g, b = pixels[x, y] | |
| for c in [r, g, b]: | |
| for i in range(BITS_PER_CHANNEL - 1, -1, -1): | |
| bits.append((c >> i) & 1) | |
| length = struct.unpack(">I", bytes_from_bits(bits[:32]))[0] | |
| if length == 0 or length > MAX_BYTES + 10: | |
| return None, None | |
| payload = bytes_from_bits(bits[32:32 + length * 8]) | |
| return length, payload | |
| def index(): | |
| return send_from_directory("static", "index.html") | |
| def encode_text(): | |
| data = request.json | |
| text = data.get("text", "") | |
| if not text: | |
| return jsonify({"error": "No text provided"}), 400 | |
| payload = b'\x00' + text.encode("utf-8") | |
| if len(payload) > MAX_BYTES: | |
| return jsonify({"error": "Text too large. Max 5MB."}), 400 | |
| try: | |
| img = embed(payload) | |
| buf = io.BytesIO() | |
| img.save(buf, format="PNG") | |
| img_b64 = base64.b64encode(buf.getvalue()).decode() | |
| w, h = img.size | |
| return jsonify({"image": img_b64, "size": f"{w}×{h}"}) | |
| except Exception as e: | |
| return jsonify({"error": str(e)}), 500 | |
| def encode_file(): | |
| if "file" not in request.files: | |
| return jsonify({"error": "No file uploaded"}), 400 | |
| f = request.files["file"] | |
| raw = f.read() | |
| if len(raw) > MAX_BYTES: | |
| return jsonify({"error": f"File too large: {len(raw)/1024/1024:.2f}MB. Max is 5MB."}), 400 | |
| fname = f.filename or "" | |
| mode = b'\x02' if fname.lower().endswith(".pdf") else b'\x01' | |
| payload = mode + raw | |
| try: | |
| img = embed(payload) | |
| buf = io.BytesIO() | |
| img.save(buf, format="PNG") | |
| img_b64 = base64.b64encode(buf.getvalue()).decode() | |
| w, h = img.size | |
| return jsonify({"image": img_b64, "size": f"{w}×{h}"}) | |
| except Exception as e: | |
| return jsonify({"error": str(e)}), 500 | |
| def decode_route(): | |
| if "file" not in request.files: | |
| return jsonify({"error": "No file uploaded"}), 400 | |
| f = request.files["file"] | |
| img = Image.open(f).convert("RGB") | |
| length, payload = extract(img) | |
| if payload is None: | |
| return jsonify({"error": "Nothing encoded or invalid image"}), 400 | |
| mode = payload[0] | |
| content = payload[1:] | |
| if mode == 0: | |
| return jsonify({"type": "text", "text": content.decode("utf-8", errors="replace")}) | |
| elif mode == 1: | |
| return jsonify({"type": "image", "image": base64.b64encode(content).decode()}) | |
| elif mode == 2: | |
| return jsonify({"type": "pdf", "pdf": base64.b64encode(content).decode()}) | |
| return jsonify({"error": "Unknown mode"}), 400 | |
| if __name__ == "__main__": | |
| app.run(host="0.0.0.0", port=7860) | |