Simplify NHL logo rendering to match weather/stock icon approach - Use direct RGBA mode for logo loading and rendering - Simplify image resizing with thumbnail method - Improve error handling and logging - Use relative positioning for better layout

This commit is contained in:
ChuckBuilds
2025-04-18 10:11:22 -05:00
parent c5e99bb9a3
commit 8850387564

View File

@@ -6,6 +6,7 @@ from PIL import Image, ImageDraw, ImageFont
import logging import logging
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import List, Dict, Optional, Any from typing import List, Dict, Optional, Any
import os
try: try:
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
except ImportError: except ImportError:
@@ -1121,224 +1122,133 @@ class NHLScoreboardManager:
msg, font=font, fill='grey') msg, font=font, fill='grey')
def _draw_upcoming_layout(self, draw: ImageDraw.ImageDraw, game_details: Dict[str, Any]): def _draw_upcoming_layout(self, draw: ImageDraw.ImageDraw, game_details: Dict[str, Any]):
"""Draws the layout for an upcoming game.""" """Draws the upcoming game layout with team logos and game time."""
font_team = self.fonts.get('team', ImageFont.load_default()) try:
font_main = self.fonts.get('upcoming_main', ImageFont.load_default()) # Load and resize logos
font_vs = self.fonts.get('upcoming_vs', ImageFont.load_default()) home_logo_path = os.path.join(self.logo_dir, f"{game_details['home_abbr']}.png")
img = draw.im away_logo_path = os.path.join(self.logo_dir, f"{game_details['away_abbr']}.png")
logging.debug("[NHL] Drawing upcoming game layout.") # Load and process home logo
home_logo = None
if os.path.exists(home_logo_path):
try:
home_logo = Image.open(home_logo_path)
if home_logo.mode != 'RGBA':
home_logo = home_logo.convert('RGBA')
# Resize maintaining aspect ratio
max_size = min(int(self.display_width / 3), int(self.display_height / 2))
home_logo.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
except Exception as e:
logging.error(f"Error loading home logo {home_logo_path}: {e}")
logo_padding = 2 # Load and process away logo
logo_max_h = self.display_height - (logo_padding * 2) away_logo = None
logo_area_width = int(self.display_width * 0.4) if os.path.exists(away_logo_path):
logo_max_w = logo_area_width - logo_padding try:
logo_size = (logo_max_w, logo_max_h) away_logo = Image.open(away_logo_path)
if away_logo.mode != 'RGBA':
away_logo = away_logo.convert('RGBA')
# Resize maintaining aspect ratio
max_size = min(int(self.display_width / 3), int(self.display_height / 2))
away_logo.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
except Exception as e:
logging.error(f"Error loading away logo {away_logo_path}: {e}")
away_logo_x = logo_padding # Calculate positions
home_logo_x = self.display_width - logo_area_width + logo_padding width = self.display_width
height = self.display_height
# Draw Away Logo # Draw team logos
if game_details.get("away_logo_path"): if home_logo:
try: home_x = width // 4 - home_logo.width // 2
# Load and resize logo home_y = height // 4 - home_logo.height // 2
away_logo = Image.open(game_details["away_logo_path"]).convert("RGBA") draw.im.paste(home_logo, (home_x, home_y), home_logo)
aspect_ratio = away_logo.width / away_logo.height
if aspect_ratio > 1: # wider than tall
new_width = min(logo_max_w, int(logo_max_h * aspect_ratio))
new_height = int(new_width / aspect_ratio)
else: # taller than wide
new_height = min(logo_max_h, int(logo_max_w / aspect_ratio))
new_width = int(new_height * aspect_ratio)
away_logo = away_logo.resize((new_width, new_height), Image.Resampling.LANCZOS) if away_logo:
paste_y = (self.display_height - new_height) // 2 away_x = width // 4 - away_logo.width // 2
away_y = 3 * height // 4 - away_logo.height // 2
draw.im.paste(away_logo, (away_x, away_y), away_logo)
# Convert to RGB before pasting # Draw game time
away_logo_rgb = away_logo.convert('RGB') game_time = game_details.get('clock', '')
img.paste(away_logo_rgb, (away_logo_x, paste_y)) time_x = width // 2 - 20
time_y = height // 2 - 8
logging.debug(f"[NHL] Successfully rendered away logo: {game_details['away_logo_path']}") draw.text((time_x, time_y), game_time, font=self.fonts['time'], fill=(255, 255, 255))
except Exception as e:
logging.error(f"[NHL] Error rendering upcoming away logo {game_details['away_logo_path']}: {e}")
draw.text((away_logo_x, 5), game_details.get("away_abbr", "?"), font=font_team, fill="white")
else:
draw.text((away_logo_x, 5), game_details.get("away_abbr", "?"), font=font_team, fill="white")
# Draw Home Logo
if game_details.get("home_logo_path"):
try:
# Load and resize logo
home_logo = Image.open(game_details["home_logo_path"]).convert("RGBA")
aspect_ratio = home_logo.width / home_logo.height
if aspect_ratio > 1: # wider than tall
new_width = min(logo_max_w, int(logo_max_h * aspect_ratio))
new_height = int(new_width / aspect_ratio)
else: # taller than wide
new_height = min(logo_max_h, int(logo_max_w / aspect_ratio))
new_width = int(new_height * aspect_ratio)
home_logo = home_logo.resize((new_width, new_height), Image.Resampling.LANCZOS)
paste_y = (self.display_height - new_height) // 2
# Convert to RGB before pasting
home_logo_rgb = home_logo.convert('RGB')
img.paste(home_logo_rgb, (home_logo_x, paste_y))
logging.debug(f"[NHL] Successfully rendered home logo: {game_details['home_logo_path']}")
except Exception as e:
logging.error(f"[NHL] Error rendering upcoming home logo {game_details['home_logo_path']}: {e}")
draw.text((home_logo_x, 5), game_details.get("home_abbr", "?"), font=font_team, fill="white")
else:
draw.text((home_logo_x, 5), game_details.get("home_abbr", "?"), font=font_team, fill="white")
# Center Text Area
center_start_x = logo_area_width
center_end_x = self.display_width - logo_area_width
center_x = (center_start_x + center_end_x) // 2
# Prepare Text
start_utc = game_details.get("start_time_utc")
date_str = "???"
time_str = "??:??"
if start_utc:
start_local = start_utc.astimezone(self.local_timezone)
now_local = datetime.now(self.local_timezone)
today_local = now_local.date()
start_date_local = start_local.date()
if start_date_local == today_local: date_str = "TODAY"
elif start_date_local == today_local + timedelta(days=1): date_str = "TOMORROW"
else: date_str = start_local.strftime("%a %b %d").upper()
time_str = start_local.strftime("%H:%M")
vs_str = "VS"
# Calculate Positions (adjust line_height as needed)
line_height_approx = font_main.getbbox("Aj")[3] - font_main.getbbox("Aj")[1] + 2
vs_height = font_vs.getbbox("VS")[3] - font_vs.getbbox("VS")[1]
total_text_height = (line_height_approx * 2) + vs_height
start_y = (self.display_height - total_text_height) // 2
date_y = start_y
time_y = date_y + line_height_approx
vs_y = time_y + line_height_approx
# Draw Text
draw.text((center_x, date_y), date_str, font=font_main, fill='white', anchor="mt")
draw.text((center_x, time_y), time_str, font=font_main, fill='white', anchor="mt")
draw.text((center_x, vs_y), vs_str, font=font_vs, fill='white', anchor="mt")
except Exception as e:
logging.error(f"Error in _draw_upcoming_layout: {e}")
def _draw_scorebug_layout(self, draw: ImageDraw.ImageDraw, game_details: Dict[str, Any]): def _draw_scorebug_layout(self, draw: ImageDraw.ImageDraw, game_details: Dict[str, Any]):
"""Draws the standard score bug layout for live or final games.""" """Draw the scorebug layout with team logos, scores, and game status."""
font_score = self.fonts.get('score', ImageFont.load_default()) try:
font_time = self.fonts.get('time', ImageFont.load_default()) # Load and resize logos
font_team = self.fonts.get('team', ImageFont.load_default()) home_logo_path = os.path.join(self.logo_dir, f"{game_details['home_abbr']}.png")
font_status = self.fonts.get('status', ImageFont.load_default()) away_logo_path = os.path.join(self.logo_dir, f"{game_details['away_abbr']}.png")
img = draw.im
logging.debug("[NHL] Drawing live/final game layout.") # Load and process home logo
home_logo = None
if os.path.exists(home_logo_path):
try:
home_logo = Image.open(home_logo_path)
if home_logo.mode != 'RGBA':
home_logo = home_logo.convert('RGBA')
# Resize maintaining aspect ratio
max_size = min(int(self.display_width / 3), int(self.display_height / 2))
home_logo.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
except Exception as e:
logging.error(f"Error loading home logo {home_logo_path}: {e}")
# Layout Calculations # Load and process away logo
logo_max_h = self.display_height - 4 away_logo = None
logo_max_w = int(self.display_width * 0.25) if os.path.exists(away_logo_path):
logo_size = (logo_max_w, logo_max_h) try:
away_logo_x = 2 away_logo = Image.open(away_logo_path)
score_width_approx = 25 if away_logo.mode != 'RGBA':
away_score_x = away_logo_x + logo_max_w + 4 away_logo = away_logo.convert('RGBA')
home_logo_x = self.display_width - logo_max_w - 2 # Resize maintaining aspect ratio
home_score_x = home_logo_x - score_width_approx - 4 max_size = min(int(self.display_width / 3), int(self.display_height / 2))
center_x = self.display_width // 2 away_logo.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
time_y = 2 except Exception as e:
period_y = 15 logging.error(f"Error loading away logo {away_logo_path}: {e}")
# --- Draw Away Team --- # Calculate positions
away_logo_drawn_size = (0,0) width = self.display_width
if game_details.get("away_logo_path"): height = self.display_height
try:
# Load and resize logo
away_logo = Image.open(game_details["away_logo_path"]).convert("RGBA")
aspect_ratio = away_logo.width / away_logo.height
if aspect_ratio > 1: # wider than tall
new_width = min(logo_max_w, int(logo_max_h * aspect_ratio))
new_height = int(new_width / aspect_ratio)
else: # taller than wide
new_height = min(logo_max_h, int(logo_max_w / aspect_ratio))
new_width = int(new_height * aspect_ratio)
away_logo = away_logo.resize((new_width, new_height), Image.Resampling.LANCZOS) # Draw team logos
away_logo_drawn_size = (new_width, new_height) if home_logo:
paste_y = (self.display_height - new_height) // 2 home_x = width // 4 - home_logo.width // 2
home_y = height // 4 - home_logo.height // 2
draw.im.paste(home_logo, (home_x, home_y), home_logo)
# Convert to RGB before pasting if away_logo:
away_logo_rgb = away_logo.convert('RGB') away_x = width // 4 - away_logo.width // 2
img.paste(away_logo_rgb, (away_logo_x, paste_y)) away_y = 3 * height // 4 - away_logo.height // 2
draw.im.paste(away_logo, (away_x, away_y), away_logo)
logging.debug(f"[NHL] Successfully rendered away logo: {game_details['away_logo_path']}") # Draw scores
except Exception as e: score_color = (255, 255, 255)
logging.error(f"[NHL] Error rendering away logo {game_details['away_logo_path']}: {e}") home_score = str(game_details['home_score'])
draw.text((away_logo_x + 2, 5), game_details.get("away_abbr", "?"), font=font_team, fill="white") away_score = str(game_details['away_score'])
else:
draw.text((away_logo_x + 2, 5), game_details.get("away_abbr", "?"), font=font_team, fill="white")
current_away_score_x = (away_logo_x + away_logo_drawn_size[0] + 4) if away_logo_drawn_size[0] > 0 else away_score_x # Draw home score
draw.text((current_away_score_x, (self.display_height - 12) // 2), str(game_details.get("away_score", "0")), font=font_score, fill='white') home_score_x = width // 2 - 10
home_score_y = height // 4 - 8
draw.text((home_score_x, home_score_y), home_score, font=self.fonts['score'], fill=score_color)
# --- Draw Home Team --- # Draw away score
home_logo_drawn_size = (0,0) away_score_x = width // 2 - 10
if game_details.get("home_logo_path"): away_score_y = 3 * height // 4 - 8
try: draw.text((away_score_x, away_score_y), away_score, font=self.fonts['score'], fill=score_color)
# Load and resize logo
home_logo = Image.open(game_details["home_logo_path"]).convert("RGBA")
aspect_ratio = home_logo.width / home_logo.height
if aspect_ratio > 1: # wider than tall
new_width = min(logo_max_w, int(logo_max_h * aspect_ratio))
new_height = int(new_width / aspect_ratio)
else: # taller than wide
new_height = min(logo_max_h, int(logo_max_w / aspect_ratio))
new_width = int(new_height * aspect_ratio)
home_logo = home_logo.resize((new_width, new_height), Image.Resampling.LANCZOS) # Draw game status
home_logo_drawn_size = (new_width, new_height) status_text = game_details.get('status', '')
paste_y = (self.display_height - new_height) // 2 status_x = width // 2 - 20
status_y = height // 2 - 8
draw.text((status_x, status_y), status_text, font=self.fonts['status'], fill=score_color)
# Convert to RGB before pasting except Exception as e:
home_logo_rgb = home_logo.convert('RGB') logging.error(f"Error in _draw_scorebug_layout: {e}")
img.paste(home_logo_rgb, (home_logo_x, paste_y))
logging.debug(f"[NHL] Successfully rendered home logo: {game_details['home_logo_path']}")
except Exception as e:
logging.error(f"[NHL] Error rendering home logo {game_details['home_logo_path']}: {e}")
draw.text((home_logo_x + 2, 5), game_details.get("home_abbr", "?"), font=font_team, fill="white")
else:
draw.text((home_logo_x + 2, 5), game_details.get("home_abbr", "?"), font=font_team, fill="white")
current_home_score_x = home_logo_x - score_width_approx - 4
draw.text((current_home_score_x, (self.display_height - 12) // 2), str(game_details.get("home_score", "0")), font=font_score, fill='white')
# --- Draw Center Info ---
if game_details.get("is_live"):
period = game_details.get('period', 0)
period_str = f"{period}{'st' if period==1 else 'nd' if period==2 else 'rd' if period==3 else 'th'}".upper() if period > 0 and period <= 3 else "OT" if period > 3 else ""
status_name = game_details.get("status_type_name", "")
clock_text = game_details.get("clock", "")
if status_name == "STATUS_HALFTIME" or "intermission" in game_details.get("status_text", "").lower():
period_str = "INTER"
clock_text = ""
draw.text((center_x, time_y), clock_text, font=font_time, fill='yellow', anchor="mt")
draw.text((center_x, period_y), period_str, font=font_time, fill='yellow', anchor="mt")
elif game_details.get("is_final"):
draw.text((center_x, time_y), "FINAL", font=font_status, fill='red', anchor="mt")
period = game_details.get('period', 0)
final_period_str = ""
if period > 3:
final_period_str = f"OT{period - 3 if period < 7 else ''}"
elif game_details.get("status_type_name") == "STATUS_SHOOTOUT":
final_period_str = "SO"
if final_period_str:
draw.text((center_x, period_y), final_period_str, font=font_time, fill='red', anchor="mt")
else: # Should not happen if logic is correct, but fallback
status_text = game_details.get("status_text", "Error")
draw.text((center_x, time_y), status_text, font=font_time, fill='grey', anchor="mt")
if __name__ == "__main__": if __name__ == "__main__":