| |
| import io |
| import logging |
| from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageStat |
| from .base import BaseGenerator |
|
|
| logger = logging.getLogger(__name__) |
|
|
| class CustomGenerator(BaseGenerator): |
| |
| def build_template(self, image_data: bytes, frame_data: bytes, char_name: str) -> Image.Image: |
| cleanup = [] |
| try: |
| canvas = Image.new("RGBA", (self.CANVAS_W, self.CANVAS_H), (0, 0, 0, 0)) |
| |
| |
| |
| frame_raw = Image.open(io.BytesIO(frame_data)).convert("RGBA") |
| cleanup.append(frame_raw) |
| frame_base = frame_raw.resize((self.CARD_W, self.CARD_H), Image.Resampling.LANCZOS) |
| cleanup.append(frame_base) |
| |
| f_w, f_h = self.CARD_W, self.CARD_H |
| alpha = frame_base.split()[3] |
|
|
| |
| try: |
| solid_pixels = alpha.point(lambda p: 255 if p > 50 else 0, mode="1") |
| stat = ImageStat.Stat(frame_base.convert("RGB"), mask=solid_pixels) |
| avg_r, avg_g, avg_b = [int(x) for x in stat.mean] |
| if max(avg_r, avg_g, avg_b) < 80: |
| avg_r, avg_g, avg_b = 160, 160, 160 |
| self.frame_color = (avg_r, avg_g, avg_b) |
| except Exception as e: |
| logger.error(f"Color extraction failed: {e}") |
| self.frame_color = None |
|
|
| |
| |
| binary_alpha = alpha.point(lambda p: 255 if p > 20 else 0, mode="L") |
| cleanup.append(binary_alpha) |
| |
| ImageDraw.floodfill(binary_alpha, (f_w // 2, f_h // 2), 128) |
| hole_mask = binary_alpha.point(lambda p: 255 if p == 128 else 0, mode="L") |
| cleanup.append(hole_mask) |
| |
| |
| bleed_mask = hole_mask.filter(ImageFilter.MaxFilter(5)).filter(ImageFilter.GaussianBlur(1.5)) |
| cleanup.append(bleed_mask) |
|
|
| |
| char_raw = Image.open(io.BytesIO(image_data)).convert("RGBA") |
| cleanup.append(char_raw) |
|
|
| bbox = hole_mask.getbbox() |
| if bbox: |
| hx1, hy1, hx2, hy2 = bbox |
| hw, hh = hx2 - hx1, hy2 - hy1 |
| else: |
| hx1, hy1, hw, hh = 0, 0, f_w, f_h |
|
|
| |
| char_fitted = ImageOps.fit(char_raw, (hw, hh), method=Image.Resampling.LANCZOS, centering=(0.5, 0.25)) |
| cleanup.append(char_fitted) |
|
|
| |
| char_full = Image.new("RGBA", (f_w, f_h), (0, 0, 0, 0)) |
| char_full.paste(char_fitted, (hx1, hy1)) |
| cleanup.append(char_full) |
| char_full.putalpha(bleed_mask) |
|
|
| composite = Image.new("RGBA", (f_w, f_h), (0, 0, 0, 0)) |
| composite.paste(char_full, (0, 0), char_full) |
| composite.paste(frame_base, (0, 0), frame_base) |
| cleanup.append(composite) |
|
|
| |
| canvas.paste(composite, (self.OFFSET_X, self.OFFSET_Y), composite) |
| return canvas |
| |
| except Exception as e: |
| logger.error(f"Cookie Cutter Frame Error: {e}", exc_info=True) |
| return Image.new("RGBA", (self.CANVAS_W, self.CANVAS_H), (0,0,0,0)) |
| |
| finally: |
| for img in cleanup: |
| try: img.close() |
| except: pass |
|
|
| def apply_text(self, canvas: Image.Image, char_name: str, print_num: int, element: str, rarity: str) -> io.BytesIO: |
| uid = ImageDraw.Draw(canvas, "RGBA") |
|
|
| card_left = self.OFFSET_X |
| card_right = self.OFFSET_X + self.CARD_W |
| card_bottom = self.OFFSET_Y + self.CARD_H |
| card_w = self.CARD_W |
|
|
| name_sz = max(24, int(card_w * 0.080)) |
| min_sz = 20 |
| name_font = self.get_font(name_sz) |
| num_font = self.get_font(int(name_sz * 0.95)) |
|
|
| r_color = self.ELEMENT_COLORS.get(element, (168, 85, 247)) |
| glow_solid = getattr(self, 'frame_color', r_color[:3]) |
|
|
| final_char_name = char_name.title() |
| |
| |
| max_name_w = int(card_w * 0.85) |
|
|
| |
| while True: |
| try: |
| n_bbox = uid.textbbox((0, 0), final_char_name, font=name_font) |
| except: |
| n_bbox = (0, 0, 20, name_sz) |
| |
| n_w = n_bbox[2] - n_bbox[0] |
| |
| if n_w <= max_name_w or name_sz <= min_sz: |
| break |
| |
| name_sz -= 2 |
| name_font = self.get_font(name_sz) |
|
|
| name_x = card_left + (card_w - n_w) // 2 |
| name_y = card_bottom - 120 |
|
|
| num_text = f"{print_num}" |
| try: num_bbox = uid.textbbox((0, 0), num_text, font=num_font) |
| except: num_bbox = (0, 0, 40, 30) |
| num_w = num_bbox[2] - num_bbox[0] |
| num_h = num_bbox[3] - num_bbox[1] |
|
|
| right_margin = max(40, int(card_w * 0.18)) |
| num_x = card_right - right_margin - num_w |
| num_x = max(card_left + right_margin, num_x) |
| num_y = name_y - num_h - 10 |
|
|
| self.draw_text_with_glow(uid, (name_x, name_y), final_char_name, name_font, core_color="white", glow_color=glow_solid) |
| self.draw_text_with_glow(uid, (num_x, num_y), num_text, num_font, core_color="white", glow_color=glow_solid) |
|
|
| buf = io.BytesIO() |
| canvas.save(buf, format="WEBP", quality=95) |
| buf.seek(0) |
| return buf |
|
|