display draw settings

fighting flicker
This commit is contained in:
Chuck
2025-04-08 18:30:08 -05:00
parent f569881394
commit 902b53ec86
2 changed files with 60 additions and 95 deletions

View File

@@ -23,11 +23,11 @@ class Clock:
self.timezone = self._get_timezone() self.timezone = self._get_timezone()
self.last_time = None self.last_time = None
self.last_date = None self.last_date = None
# Colors for different elements # Colors for different elements - using brighter colors
self.COLORS = { self.COLORS = {
'time': (255, 255, 255), # White for time 'time': (255, 255, 255), # Bright white for time
'ampm': (200, 200, 200), # Light gray for AM/PM 'ampm': (200, 200, 255), # Light blue for AM/PM
'date': (150, 150, 150) # Darker gray for date 'date': (255, 200, 200) # Light red for date
} }
def _get_timezone(self) -> pytz.timezone: def _get_timezone(self) -> pytz.timezone:
@@ -108,21 +108,21 @@ class Clock:
display_width = self.display_manager.matrix.width display_width = self.display_manager.matrix.width
display_height = self.display_manager.matrix.height display_height = self.display_manager.matrix.height
# Draw time (large, centered) # Draw time (large, centered, near top)
self.display_manager.draw_text( self.display_manager.draw_text(
time_str, time_str,
y=4, # Near top y=2, # Slightly higher
color=self.COLORS['time'], color=self.COLORS['time'],
small_font=False small_font=False
) )
# Draw AM/PM (small, next to time) # Draw AM/PM (small, next to time)
time_width = self.display_manager.font.getlength(time_str) time_width = self.display_manager.font.getlength(time_str)
ampm_x = (display_width + time_width) // 2 + 2 # Right of time ampm_x = (display_width + time_width) // 2 + 1 # Closer to time
self.display_manager.draw_text( self.display_manager.draw_text(
ampm, ampm,
x=ampm_x, x=ampm_x,
y=6, # Align with time y=4, # Align better with time
color=self.COLORS['ampm'], color=self.COLORS['ampm'],
small_font=True small_font=True
) )
@@ -130,7 +130,7 @@ class Clock:
# Draw date (small, centered below time) # Draw date (small, centered below time)
self.display_manager.draw_text( self.display_manager.draw_text(
date_str, date_str,
y=display_height - 10, # Near bottom y=display_height - 8, # Slightly higher from bottom
color=self.COLORS['date'], color=self.COLORS['date'],
small_font=True small_font=True
) )

View File

@@ -1,7 +1,7 @@
from rgbmatrix import RGBMatrix, RGBMatrixOptions from rgbmatrix import RGBMatrix, RGBMatrixOptions
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
import time import time
from typing import Dict, Any, List from typing import Dict, Any, List, Tuple
import logging import logging
import math import math
from .weather_icons import WeatherIcons from .weather_icons import WeatherIcons
@@ -19,15 +19,11 @@ class DisplayManager:
cls._instance = super(DisplayManager, cls).__new__(cls) cls._instance = super(DisplayManager, cls).__new__(cls)
return cls._instance return cls._instance
def __init__(self, config: Dict[str, Any]): def __init__(self, config: Dict[str, Any] = None):
# Only initialize once self.config = config or {}
if not DisplayManager._initialized: self._setup_matrix()
self.config = config self._load_fonts()
logger.info("Initializing DisplayManager with config: %s", config)
self._setup_matrix()
self._load_fonts()
DisplayManager._initialized = True
def _setup_matrix(self): def _setup_matrix(self):
"""Initialize the RGB matrix with configuration settings.""" """Initialize the RGB matrix with configuration settings."""
options = RGBMatrixOptions() options = RGBMatrixOptions()
@@ -35,40 +31,40 @@ class DisplayManager:
# Hardware configuration # Hardware configuration
hardware_config = self.config.get('hardware', {}) hardware_config = self.config.get('hardware', {})
options.rows = hardware_config.get('rows', 32) options.rows = hardware_config.get('rows', 32)
options.cols = hardware_config.get('cols', 64) # Each panel is 64 columns options.cols = hardware_config.get('cols', 64)
options.chain_length = hardware_config.get('chain_length', 2) options.chain_length = hardware_config.get('chain_length', 2)
options.parallel = hardware_config.get('parallel', 1) options.parallel = hardware_config.get('parallel', 1)
options.hardware_mapping = hardware_config.get('hardware_mapping', 'adafruit-hat-pwm') options.hardware_mapping = hardware_config.get('hardware_mapping', 'adafruit-hat-pwm')
logger.info("Setting hardware mapping to: %s", options.hardware_mapping)
options.brightness = hardware_config.get('brightness', 60) # Increase brightness and optimize display quality
options.pwm_bits = hardware_config.get('pwm_bits', 8) options.brightness = hardware_config.get('brightness', 100) # Maximum brightness
options.pwm_bits = hardware_config.get('pwm_bits', 11) # Maximum color depth
options.pwm_lsb_nanoseconds = hardware_config.get('pwm_lsb_nanoseconds', 130) options.pwm_lsb_nanoseconds = hardware_config.get('pwm_lsb_nanoseconds', 130)
options.led_rgb_sequence = hardware_config.get('led_rgb_sequence', 'RGB') options.led_rgb_sequence = hardware_config.get('led_rgb_sequence', 'RGB')
options.pixel_mapper_config = hardware_config.get('pixel_mapper_config', '') options.pixel_mapper_config = hardware_config.get('pixel_mapper_config', '')
options.row_address_type = hardware_config.get('row_addr_type', 0) options.row_address_type = hardware_config.get('row_addr_type', 0)
options.multiplexing = hardware_config.get('multiplexing', 0) options.multiplexing = hardware_config.get('multiplexing', 0)
options.disable_hardware_pulsing = hardware_config.get('disable_hardware_pulsing', True) options.disable_hardware_pulsing = True # Reduce flickering
options.show_refresh_rate = hardware_config.get('show_refresh_rate', True) options.show_refresh_rate = False
options.limit_refresh_rate_hz = hardware_config.get('limit_refresh_rate_hz', 100) options.limit_refresh_rate_hz = hardware_config.get('limit_refresh_rate_hz', 120) # Higher refresh rate
# Runtime configuration # Runtime configuration
runtime_config = self.config.get('runtime', {}) runtime_config = self.config.get('runtime', {})
options.gpio_slowdown = runtime_config.get('gpio_slowdown', 2) options.gpio_slowdown = runtime_config.get('gpio_slowdown', 1) # Reduce slowdown
logger.info("Setting GPIO slowdown to: %d", options.gpio_slowdown)
# Initialize the matrix
logger.info("Initializing RGB matrix with options...")
self.matrix = RGBMatrix(options=options)
logger.info("RGB matrix initialized successfully")
logger.info(f"Matrix dimensions: {self.matrix.width}x{self.matrix.height}")
# Create double buffer # Initialize the matrix
self.matrix = RGBMatrix(options=options)
# Create double buffer for smooth updates
self.offscreen_canvas = self.matrix.CreateFrameCanvas() self.offscreen_canvas = self.matrix.CreateFrameCanvas()
# Create image with full chain width # Create image with full chain width
self.image = Image.new('RGB', (self.matrix.width, self.matrix.height)) self.image = Image.new('RGB', (self.matrix.width, self.matrix.height))
self.draw = ImageDraw.Draw(self.image) self.draw = ImageDraw.Draw(self.image)
# Set matrix to use luminance correction for better color reproduction
self.matrix.set_luminance_correct(True)
# Initialize font # Initialize font
try: try:
self.font = ImageFont.truetype("DejaVuSans.ttf", 14) self.font = ImageFont.truetype("DejaVuSans.ttf", 14)
@@ -101,16 +97,17 @@ class DisplayManager:
time.sleep(2) time.sleep(2)
def update_display(self): def update_display(self):
"""Update the display using double buffering.""" """Update the display using double buffering for smooth transitions."""
# Copy the current image to the offscreen canvas # Copy the current image to the offscreen canvas
self.offscreen_canvas.SetImage(self.image) self.offscreen_canvas.SetImage(self.image)
# Swap the canvases # Swap the canvases on VSync for smooth transition
self.offscreen_canvas = self.matrix.SwapOnVSync(self.offscreen_canvas) self.offscreen_canvas = self.matrix.SwapOnVSync(self.offscreen_canvas)
def clear(self): def clear(self):
"""Clear the display.""" """Clear the display completely."""
self.draw.rectangle((0, 0, self.matrix.width, self.matrix.height), fill=(0, 0, 0)) self.image = Image.new('RGB', (self.matrix.width, self.matrix.height))
self.update_display() self.draw = ImageDraw.Draw(self.image)
self.update_display() # Ensure the clear is displayed
def _load_fonts(self): def _load_fonts(self):
"""Load fonts for different text sizes.""" """Load fonts for different text sizes."""
@@ -126,68 +123,36 @@ class DisplayManager:
self.font = ImageFont.load_default() self.font = ImageFont.load_default()
self.small_font = self.font self.small_font = self.font
def draw_text(self, text: str, x: int = None, y: int = None, color: tuple = (255, 255, 255), def draw_text(self, text: str, x: int = None, y: int = None, color: Tuple[int, int, int] = (255, 255, 255), small_font: bool = False) -> None:
force_clear: bool = False, small_font: bool = False): """Draw text on the display with improved visibility."""
"""Draw text on the display with automatic centering.""" # Ensure maximum brightness for text
if force_clear: if isinstance(color, tuple) and len(color) == 3:
self.clear() # Increase brightness of colors while maintaining relative ratios
else: color = tuple(min(255, int(c * 1.2)) for c in color)
# Just create a new blank image without updating display
self.image = Image.new('RGB', (self.matrix.width, self.matrix.height))
self.draw = ImageDraw.Draw(self.image)
# Select font based on small_font parameter
font = self.small_font if small_font else self.font font = self.small_font if small_font else self.font
# Split text into lines if it contains newlines # Get text dimensions for centering if x not specified
lines = text.split('\n') bbox = self.draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
# Calculate total height of all lines # Center text horizontally if x not specified
line_heights = [] if x is None:
line_widths = [] x = (self.matrix.width - text_width) // 2
total_height = 0
padding = 2 # Add padding between lines
edge_padding = 2 # Minimum padding from display edges
for line in lines: # Center text vertically if y not specified
bbox = self.draw.textbbox((0, 0), line, font=font)
line_width = bbox[2] - bbox[0]
line_height = bbox[3] - bbox[1]
line_heights.append(line_height)
line_widths.append(line_width)
total_height += line_height
# Add padding between lines
if len(lines) > 1:
total_height += padding * (len(lines) - 1)
# Calculate starting Y position to center all lines vertically
if y is None: if y is None:
y = max(edge_padding, (self.matrix.height - total_height) // 2) text_height = bbox[3] - bbox[1]
y = (self.matrix.height - text_height) // 2
# Draw each line # Draw text with slight glow effect for better visibility
current_y = y # Draw shadow/glow
for i, line in enumerate(lines): shadow_offset = 1
if x is None: shadow_color = tuple(max(0, int(c * 0.3)) for c in color)
# Center this line horizontally self.draw.text((x + shadow_offset, y + shadow_offset), text, font=font, fill=shadow_color)
line_x = (self.matrix.width - line_widths[i]) // 2
else:
line_x = x
# Ensure x coordinate stays within bounds
line_x = max(edge_padding, min(line_x, self.matrix.width - line_widths[i] - edge_padding))
# Ensure y coordinate stays within bounds
current_y = max(edge_padding, min(current_y, self.matrix.height - line_heights[i] - edge_padding))
# Draw the text (removed logging to reduce spam)
self.draw.text((line_x, current_y), line, font=font, fill=color)
# Calculate next line position
current_y += line_heights[i] + padding
# Update the display using double buffering # Draw main text
self.update_display() self.draw.text((x, y), text, font=font, fill=color)
def draw_scrolling_text(self, text: str, scroll_position: int, force_clear: bool = False) -> None: def draw_scrolling_text(self, text: str, scroll_position: int, force_clear: bool = False) -> None:
"""Draw scrolling text on the display.""" """Draw scrolling text on the display."""