Browse Source

Fix nozzle rack text unreadable on light filament colors (#300)

Nozzle rack slots use the filament color as background, but light
colors (white, beige) made the white "0.4" text nearly invisible.
Added a luminance check to switch to dark text on light backgrounds
and removed the dark text shadow when not needed.
maziggy 3 months ago
parent
commit
253bee18a1
2 changed files with 12 additions and 2 deletions
  1. 1 0
      CHANGELOG.md
  2. 11 2
      frontend/src/pages/PrintersPage.tsx

+ 1 - 0
CHANGELOG.md

@@ -6,6 +6,7 @@ All notable changes to Bambuddy will be documented in this file.
 
 ### Fixed
 - **External Camera Not Used for Snapshot + Stream Dropping** ([#325](https://github.com/maziggy/bambuddy/issues/325)) — The snapshot endpoint (`/camera/snapshot`) always used the internal printer camera even when an external camera was configured. Now checks for external camera first, matching the existing stream endpoint behavior. Also fixed external MJPEG and RTSP streams silently dropping every ~60 seconds due to missing reconnect logic — the underlying stream generators exit on read timeout, and the caller now retries up to 3 times with a 2-second delay instead of ending the stream.
+- **H2C Nozzle Rack Text Unreadable on Light Filament Colors** ([#300](https://github.com/maziggy/bambuddy/issues/300)) — Nozzle rack slots use the loaded filament color as background, but white/light filaments made the white "0.4" text nearly invisible. Now uses a luminance check to switch to dark text on light backgrounds.
 
 ## [0.1.9] - 2026-02-10
 

+ 11 - 2
frontend/src/pages/PrintersPage.tsx

@@ -421,6 +421,14 @@ function parseFilamentColor(rgba: string): string | null {
   return `rgba(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}, ${a})`;
 }
 
+function isLightFilamentColor(rgba: string): boolean {
+  if (!rgba || rgba.length < 6) return false;
+  const r = parseInt(rgba.slice(0, 2), 16);
+  const g = parseInt(rgba.slice(2, 4), 16);
+  const b = parseInt(rgba.slice(4, 6), 16);
+  return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.6;
+}
+
 // Expand nozzle type codes to material names
 // Handles full text ("hardened_steel"), 2-char codes ("HS"/"HH"), and 4-char codes ("HS01")
 // Material mapping: 00=stainless steel, 01=hardened steel, 05=tungsten carbide
@@ -798,6 +806,7 @@ function NozzleRackCard({ slots, filamentInfo }: { slots: import('../api/client'
         {rackSlots.map((slot, i) => {
           const isEmpty = !slot.nozzle_diameter && !slot.nozzle_type;
           const filamentBg = !isEmpty ? parseFilamentColor(slot.filament_color) : null;
+          const lightBg = filamentBg ? isLightFilamentColor(slot.filament_color) : false;
 
           return (
             <NozzleSlotHoverCard key={slot.id >= 0 ? slot.id : `empty-${i}`} slot={slot} index={i} filamentName={slot.filament_id ? filamentInfo?.[slot.filament_id]?.name : undefined}>
@@ -809,8 +818,8 @@ function NozzleRackCard({ slots, filamentInfo }: { slots: import('../api/client'
                 }`}
                 style={filamentBg ? { backgroundColor: filamentBg } : undefined}
               >
-                <span className={`text-[10px] font-semibold ${isEmpty ? 'text-bambu-gray/30' : 'text-white'}`}
-                      style={filamentBg ? { textShadow: '0 1px 3px rgba(0,0,0,0.9)' } : undefined}
+                <span className={`text-[10px] font-semibold ${isEmpty ? 'text-bambu-gray/30' : lightBg ? 'text-black/80' : 'text-white'}`}
+                      style={filamentBg && !lightBg ? { textShadow: '0 1px 3px rgba(0,0,0,0.9)' } : undefined}
                 >
                   {isEmpty ? '—' : (slot.nozzle_diameter || '?')}
                 </span>