import io import os import urllib.parse from flask import Flask, request, send_file, render_template_string from PIL import Image, ImageFont, ImageDraw, ImageFilter from pilmoji import Pilmoji app = Flask(__name__) # ========================================== # UI HTML (Mobile Friendly) # ========================================== HTML_TEMPLATE = """ Brat Generator

Brat Generator ⬜

Hasil Render
⬇️ Download Gambar
""" # ========================================== # Fungsi Pencari Font Fleksibel # ========================================== def get_font(size): # Mencari Arial atau font mirip Arial di berbagai OS font_paths = [ "/usr/share/fonts/truetype/msttcorefonts/Arial.ttf", # Ubuntu/Linux Arial "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", # Linux Fallback "C:\\Windows\\Fonts\\arial.ttf", # Windows "/System/Library/Fonts/Supplemental/Arial.ttf", # Mac OS ] for path in font_paths: if os.path.exists(path): try: return ImageFont.truetype(path, size) except: continue return ImageFont.load_default() # ========================================== # Algoritma Auto-Scaling yang Diperbaiki (Struktur Tetap Terjaga) # ========================================== def get_optimal_font_and_text(text, max_width, max_height, line_spacing=10): # BATASAN UKURAN FONT: # Dimulai dari 75px (tidak 180px) agar kalimat seperti "Ini adalah" # tidak terpecah menjadi 2 baris karena font terlalu besar. max_font_size = 75 min_font_size = 15 # Dummy image untuk menghitung text bounding box temp_img = Image.new('RGB', (1, 1)) draw = ImageDraw.Draw(temp_img) for size in range(max_font_size, min_font_size, -1): font = get_font(size) lines = [] # Menghormati tombol "Enter" yang diketik user for paragraph in text.split('\n'): words = paragraph.split(' ') current_line = [] for word in words: test_line = ' '.join(current_line + [word]) if current_line else word if font.getlength(test_line) <= max_width: current_line.append(word) else: if current_line: lines.append(' '.join(current_line)) current_line = [word] else: lines.append(word) current_line = [] if current_line: lines.append(' '.join(current_line)) wrapped_text = '\n'.join(lines) # Cek apakah blok teks (termasuk spasi antar baris) muat di layar bbox = draw.multiline_textbbox((0, 0), wrapped_text, font=font, align="center", spacing=line_spacing) text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] if text_w <= max_width and text_h <= max_height: return font, wrapped_text return font, wrapped_text # ========================================== # Routes # ========================================== @app.route('/') def index(): return render_template_string(HTML_TEMPLATE) @app.route('/generate') def generate(): text = request.args.get('text', 'Ini adalah\ncontoh\nbrat😭').strip() if not text: text = "Ini adalah\ncontoh\nbrat😭" # Konfigurasi Canvas 1:1 (512x512) img_size = 512 # Padding diperbesar (60px) agar area teks tidak terlalu menempel ke pinggir layar padding = 60 line_spacing = 15 # Jarak antar baris teks # Warna Canvas (Putih Murni) & Warna Teks (Abu-abu Gelap, mencegah kontras berlebih) bg_color = (255, 255, 255) text_color = (40, 40, 40) img = Image.new('RGB', (img_size, img_size), color=bg_color) max_text_width = img_size - (padding * 2) max_text_height = img_size - (padding * 2) # Dapatkan font size ideal yang menjaga struktur kalimat font, wrapped_text = get_optimal_font_and_text(text, max_text_width, max_text_height, line_spacing) # Rendering Teks dengan Pilmoji with Pilmoji(img) as pilmoji: pilmoji.text( (img_size // 2, img_size // 2), wrapped_text, fill=text_color, font=font, anchor="mm", # Fix posisi benar-benar di tengah absolut align="center", # Rata tengah untuk tiap baris spacing=line_spacing, # Spasi antar baris emoji_scale_factor=1.05 # Skala emoji sedikit dikecilkan agar seimbang ) # =============== EFEK BLUR (LOW QUALITY VIBE) ================= # Radius 0.5 memberi efek 'soft' persis kompresi gambar WhatsApp/Meme img = img.filter(ImageFilter.GaussianBlur(radius=0.5)) # ============================================================== # Output memori img_io = io.BytesIO() img.save(img_io, 'PNG') img_io.seek(0) return send_file(img_io, mimetype='image/png') if __name__ == '__main__': app.run(host='0.0.0.0', port=7860)