| 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__) |
|
|
| |
| |
| |
| HTML_TEMPLATE = """ |
| <!DOCTYPE html> |
| <html lang="id"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Brat Generator</title> |
| <style> |
| body { |
| font-family: Arial, sans-serif; |
| background-color: #f4f4f9; |
| margin: 0; |
| padding: 20px; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| min-height: 100vh; |
| } |
| .container { |
| background: white; |
| padding: 25px; |
| border-radius: 12px; |
| box-shadow: 0 4px 15px rgba(0,0,0,0.1); |
| width: 100%; |
| max-width: 400px; |
| box-sizing: border-box; |
| } |
| h2 { text-align: center; margin-top: 0; color: #333; } |
| textarea { |
| width: 100%; |
| height: 100px; |
| padding: 12px; |
| box-sizing: border-box; |
| border: 2px solid #ddd; |
| border-radius: 8px; |
| font-size: 16px; |
| resize: none; |
| margin-bottom: 15px; |
| } |
| button { |
| width: 100%; |
| background-color: #111; |
| color: white; |
| border: none; |
| padding: 14px; |
| font-size: 16px; |
| font-weight: bold; |
| border-radius: 8px; |
| cursor: pointer; |
| transition: background 0.2s; |
| } |
| button:hover { background-color: #333; } |
| .result-container { |
| margin-top: 20px; |
| text-align: center; |
| } |
| img { |
| max-width: 100%; |
| border: 1px solid #eaeaea; |
| border-radius: 4px; |
| display: block; |
| margin: 0 auto; |
| } |
| .download-btn { |
| display: inline-block; |
| margin-top: 15px; |
| color: #111; |
| text-decoration: none; |
| font-weight: bold; |
| font-size: 14px; |
| } |
| </style> |
| </head> |
| <body> |
| |
| <div class="container"> |
| <h2>Brat Generator ⬜</h2> |
| <textarea id="inputText" placeholder="Ini adalah\ncontoh\nbrat😭">Ini adalah contoh brat😭</textarea> |
| <button onclick="generateImage()">Buat Gambar</button> |
| |
| <div class="result-container"> |
| <img id="resultImg" src="/generate?text=Ini%20adalah%0Acontoh%0Abrat%F0%9F%98%AD" alt="Hasil Render"> |
| <br> |
| <a id="downloadLink" class="download-btn" href="/generate?text=Ini%20adalah%0Acontoh%0Abrat%F0%9F%98%AD" download="brat.png">⬇️ Download Gambar</a> |
| </div> |
| </div> |
| |
| <script> |
| function generateImage() { |
| const text = document.getElementById('inputText').value; |
| const encodedText = encodeURIComponent(text); |
| const url = `/generate?text=${encodedText}`; |
| |
| document.getElementById('resultImg').src = url; |
| document.getElementById('downloadLink').href = url; |
| } |
| </script> |
| |
| </body> |
| </html> |
| """ |
|
|
| |
| |
| |
| def get_font(size): |
| |
| font_paths = [ |
| "/usr/share/fonts/truetype/msttcorefonts/Arial.ttf", |
| "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", |
| "C:\\Windows\\Fonts\\arial.ttf", |
| "/System/Library/Fonts/Supplemental/Arial.ttf", |
| ] |
| for path in font_paths: |
| if os.path.exists(path): |
| try: |
| return ImageFont.truetype(path, size) |
| except: |
| continue |
| return ImageFont.load_default() |
|
|
| |
| |
| |
| def get_optimal_font_and_text(text, max_width, max_height, line_spacing=10): |
| |
| |
| |
| max_font_size = 75 |
| min_font_size = 15 |
| |
| |
| 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 = [] |
| |
| 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) |
| |
| |
| 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 |
|
|
| |
| |
| |
| @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😭" |
|
|
| |
| img_size = 512 |
| |
| padding = 60 |
| line_spacing = 15 |
| |
| |
| 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) |
| |
| |
| font, wrapped_text = get_optimal_font_and_text(text, max_text_width, max_text_height, line_spacing) |
|
|
| |
| with Pilmoji(img) as pilmoji: |
| pilmoji.text( |
| (img_size // 2, img_size // 2), |
| wrapped_text, |
| fill=text_color, |
| font=font, |
| anchor="mm", |
| align="center", |
| spacing=line_spacing, |
| emoji_scale_factor=1.05 |
| ) |
|
|
| |
| |
| img = img.filter(ImageFilter.GaussianBlur(radius=0.5)) |
| |
|
|
| |
| 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) |