Spaces:
Runtime error
Runtime error
| # modules/art_tools.py | |
| import os | |
| import math | |
| import numpy as np | |
| from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageOps | |
| import gradio as gr | |
| import io | |
| # --- ADOPTABLE GRID MAKER --- | |
| def hex_to_rgb(hex_color): | |
| """Hex color kodunu (ör: #FF0000) (255, 0, 0) tuple'ına çevirir.""" | |
| hex_color = hex_color.lstrip('#') | |
| return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) + (255,) # Alpha ekle | |
| def create_grid_collage(files, columns, add_labels, label_type, start_number, bg_color_hex, title_text, banner_color_hex, title_color_hex): | |
| """ | |
| Adoptable Style Grid Collage Maker | |
| """ | |
| if not files: | |
| return None, "⚠️ Lütfen resim dosyalarını seçin." | |
| try: | |
| images = [] | |
| for file_obj in files: | |
| try: | |
| img = Image.open(file_obj.name).convert("RGBA") | |
| images.append(img) | |
| except: | |
| continue | |
| if not images: | |
| return None, "⚠️ Geçerli resim bulunamadı." | |
| count = len(images) | |
| cols = int(columns) | |
| rows = math.ceil(count / cols) | |
| # 1. Boyut Standardizasyonu (İlk resme göre) | |
| base_w, base_h = images[0].size | |
| resized_images = [] | |
| for img in images: | |
| if img.size != (base_w, base_h): | |
| resized_images.append(img.resize((base_w, base_h), Image.Resampling.LANCZOS)) | |
| else: | |
| resized_images.append(img) | |
| # Tasarım Parametreleri | |
| padding = 40 # Resimler arası boşluk biraz arttı | |
| frame_width = 15 # Çerçeve kalınlığı arttı | |
| # Renkler | |
| bg_color = hex_to_rgb(bg_color_hex) | |
| banner_color = hex_to_rgb(banner_color_hex) | |
| title_color = hex_to_rgb(title_color_hex) | |
| frame_color = title_color # Çerçeve rengi başlık rengiyle aynı olsun | |
| # Canvas Boyutu (Önce hesapla) | |
| grid_w = (cols * base_w) + ((cols + 1) * padding) | |
| # Header Yüksekliği: Grid genişliğine orantılı olsun (Genişlik arttıkça yükseklik de artsın ki text büyüyebilsin) | |
| # Min 250px, yoksa genişliğin %15'i | |
| header_height = max(250, int(grid_w * 0.15)) | |
| grid_h = header_height + (rows * base_h) + ((rows + 1) * padding) | |
| grid_img = Image.new("RGBA", (grid_w, grid_h), bg_color) | |
| draw = ImageDraw.Draw(grid_img) | |
| # --- HEADER (BANNER) ÇİZİMİ --- | |
| draw.rectangle([(0, 0), (grid_w, header_height)], fill=banner_color) | |
| draw.rectangle([(0, header_height-15), (grid_w, header_height)], fill=frame_color) | |
| # Başlangıç Font Boyutu (Çok daha büyük başlıyoruz - Grid genişliğine orantılı) | |
| font_title_size = int(grid_w * 0.1) # Genişliğin %10'u ile başla | |
| # --- BAŞLIK FONTU BÜYÜKLÜĞÜNÜ AYARLA (Agresif Scaling) --- | |
| target_width = grid_w * 0.95 # Banner genişliğinin %95'ini doldurmalı | |
| max_height_text = header_height * 0.8 # Yüksekliğin %80'ini geçmesin (Hava kalsın) | |
| try: | |
| font_path = "timesbd.ttf" | |
| font_title = ImageFont.truetype(font_path, font_title_size) | |
| except: | |
| font_path = None | |
| font_title = ImageFont.load_default() | |
| if font_path: | |
| # İterasyonla en uygun boyutu bul | |
| for _ in range(50): # Sonsuz döngüden kaçınmak için limitli | |
| bbox_title = draw.textbbox((0, 0), title_text, font=font_title) | |
| w_total = (bbox_title[2] - bbox_title[0]) | |
| h_max = bbox_title[3] - bbox_title[1] | |
| # Çok büyükse küçült | |
| if w_total > target_width or h_max > max_height_text: | |
| font_title_size -= 5 | |
| if font_title_size < 20: break | |
| font_title = ImageFont.truetype(font_path, font_title_size) | |
| # Çok küçükse büyüt (Eğer dikeyde ve yatayda yer varsa) | |
| elif w_total < target_width * 0.9 and h_max < max_height_text * 0.9: | |
| font_title_size += 5 | |
| font_title = ImageFont.truetype(font_path, font_title_size) | |
| else: | |
| break # Tamamdır | |
| # Son Ölçümler & Çizim | |
| bbox_title = draw.textbbox((0, 0), title_text, font=font_title) | |
| w_title = bbox_title[2] - bbox_title[0] | |
| h_title = bbox_title[3] - bbox_title[1] | |
| start_x_text = (grid_w - w_title) // 2 | |
| bg_center_y = header_height // 2 | |
| # Başlık çiz | |
| draw.text((start_x_text, bg_center_y - h_title//2), title_text, font=font_title, fill=title_color, stroke_width=int(font_title_size*0.02), stroke_fill="black") | |
| # --- RESİMLERİ YERLEŞTİR --- | |
| # Numara Fontunu Resim Boyutuna Göre Ayarla (Çok Büyük) | |
| try: | |
| num_font_size = int(base_w * 0.25) # Resim genişliğinin %25'i kadar (Kocaman) | |
| font_num = ImageFont.truetype("impact.ttf", num_font_size) | |
| except: | |
| font_num = ImageFont.load_default() | |
| current_num = int(start_number) | |
| for i, img in enumerate(resized_images): | |
| r = i // cols | |
| c = i % cols | |
| x = padding + (c * (base_w + padding)) | |
| y = header_height + padding + (r * (base_h + padding)) | |
| # Kalın Çerçeve | |
| draw.rectangle( | |
| [(x - frame_width, y - frame_width), | |
| (x + base_w + frame_width, y + base_h + frame_width)], | |
| fill=None, outline=frame_color, width=frame_width | |
| ) | |
| # Resim | |
| grid_img.paste(img, (x, y), mask=img) | |
| # --- OVERLAY NUMARA (DEVASA) --- | |
| if add_labels: | |
| if str(label_type).startswith("Sadece"): | |
| num_str = f"{current_num}" | |
| else: | |
| num_str = f"#{current_num}" | |
| current_num += 1 | |
| bbox_num = draw.textbbox((0, 0), num_str, font=font_num) | |
| w_num = bbox_num[2] - bbox_num[0] | |
| h_num = bbox_num[3] - bbox_num[1] | |
| # Ortala ve Biraz daha yukarı al (Resmin altından %10 yukarıda) | |
| num_x = x + (base_w - w_num) // 2 | |
| num_y = y + base_h - h_num - int(base_h * 0.05) - 20 | |
| # Çok Kalın Outline | |
| outline_width = int(num_font_size * 0.08) # Font boyutuna göre outline kalınlığı | |
| draw.text((num_x, num_y), num_str, font=font_num, fill="white", stroke_width=outline_width, stroke_fill="black") | |
| return grid_img, "✅ Adoptable Grid oluşturuldu!" | |
| except Exception as e: | |
| import traceback | |
| traceback.print_exc() | |
| return None, f"❌ Hata: {str(e)}" |