Ver Fonte

Replace logo bitmap with animated polygon logo

Green and blue shapes fade in sequentially over 2 s using a scanline
polygon fill, then hold for 8 s. Colour fades are computed per-frame
via RGB565 helpers; scaled screen-space points are cached on first call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Benjamin Harris há 2 semanas atrás
pai
commit
23a9c7d62a
3 ficheiros alterados com 118 adições e 34 exclusões
  1. 3 1
      src/config.h
  2. 112 31
      src/logo.h
  3. 3 2
      src/main.ino

+ 3 - 1
src/config.h

@@ -24,7 +24,9 @@
 // ---------------------------------------------------------------------------
 // Timing
 // ---------------------------------------------------------------------------
-#define LOGO_HOLD_MS        10000UL
+#define LOGO_FADE_STEPS     100         // animation steps per fade phase
+#define LOGO_FADE_STEP_MS   10          // ms per step (phase duration = 1 s each)
+#define LOGO_STATIC_MS      8000UL      // ms logo holds after both fades complete
 #define POLL_INTERVAL_MS    50UL
 
 // ---------------------------------------------------------------------------

+ 112 - 31
src/logo.h

@@ -3,38 +3,119 @@
 #include "config.h"
 
 // ---------------------------------------------------------------------------
-// Startup logo rendered via TFT_eSPI sprites.
-// Replace drawLogo() with custom artwork stored in PROGMEM if required.
+// Logo polygon data — design canvas 107 × 125 units
 // ---------------------------------------------------------------------------
+static const int kGreenPts[][2] = {
+    {19, 92}, {19, 39}, {54, 66}, {107, 24},
+    {107,  1}, {54, 42}, {  1,  2}, {  1, 107}
+};
 
+static const int kBluePts[][2] = {
+    {  1, 125}, {31, 125}, {89,  79}, { 89, 107},
+    { 76, 107}, {76, 125}, {107, 125}, {107,  42}
+};
+
+static constexpr int   LOGO_N      = 8;
+static constexpr float LOGO_SCALE  = 1.6f;
+static constexpr int   LOGO_X_OFF  = 34;
+static constexpr int   LOGO_Y_OFF  = 20;
+
+static inline int lsx(int x) { return LOGO_X_OFF + (int)(x * LOGO_SCALE); }
+static inline int lsy(int y) { return LOGO_Y_OFF + (int)(y * LOGO_SCALE); }
+
+// ---------------------------------------------------------------------------
+// RGB565 colour helpers
+// ---------------------------------------------------------------------------
+static inline uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b) {
+    return ((uint16_t)(r & 0xF8) << 8) |
+           ((uint16_t)(g & 0xFC) << 3) |
+            (b >> 3);
+}
+
+static inline uint16_t logoGreenAt(float t) {
+    return rgb565(0, (uint8_t)(191.0f * t), (uint8_t)(99.0f * t));
+}
+
+static inline uint16_t logoBlueAt(float t) {
+    return rgb565((uint8_t)(17.0f * t), (uint8_t)(67.0f * t), (uint8_t)(120.0f * t));
+}
+
+// ---------------------------------------------------------------------------
+// Scanline polygon fill — operates in screen coordinates
+// ---------------------------------------------------------------------------
+static void fillPolygon(TFT_eSPI &tft, int pts[][2], int n, uint16_t colour) {
+    int yMin = pts[0][1], yMax = pts[0][1];
+    for (int i = 1; i < n; i++) {
+        if (pts[i][1] < yMin) yMin = pts[i][1];
+        if (pts[i][1] > yMax) yMax = pts[i][1];
+    }
+
+    int nodeX[16]; // 8-point polygon produces at most 8 intersections per line
+
+    for (int y = yMin; y <= yMax; y++) {
+        int nodes = 0;
+        int j = n - 1;
+        for (int i = 0; i < n; i++) {
+            int yi = pts[i][1], yj = pts[j][1];
+            if ((yi < y && yj >= y) || (yj < y && yi >= y)) {
+                int dy = yj - yi;
+                nodeX[nodes++] = pts[i][0] + (y - yi) * (pts[j][0] - pts[i][0]) / dy;
+            }
+            j = i;
+        }
+        // Insertion sort
+        for (int a = 1; a < nodes; a++) {
+            int key = nodeX[a], b = a - 1;
+            while (b >= 0 && nodeX[b] > key) { nodeX[b + 1] = nodeX[b--]; }
+            nodeX[b + 1] = key;
+        }
+        for (int a = 0; a + 1 < nodes; a += 2) {
+            tft.drawFastHLine(nodeX[a], y, nodeX[a + 1] - nodeX[a] + 1, colour);
+        }
+    }
+}
+
+// Scale design-space polygon into screen-space once
+static void buildScaledLogo(int greenOut[][2], int blueOut[][2]) {
+    for (int i = 0; i < LOGO_N; i++) {
+        greenOut[i][0] = lsx(kGreenPts[i][0]);
+        greenOut[i][1] = lsy(kGreenPts[i][1]);
+        blueOut[i][0]  = lsx(kBluePts[i][0]);
+        blueOut[i][1]  = lsy(kBluePts[i][1]);
+    }
+}
+
+// ---------------------------------------------------------------------------
+// Animated logo — total duration ~10 s
+//   0.0 – 1.0 s  green shape fades in
+//   1.0 – 2.0 s  blue shape fades in, green holds solid
+//   2.0 – 10.0 s logo remains on screen
+// ---------------------------------------------------------------------------
 inline void drawLogo(TFT_eSPI &tft) {
-    // Use a full-screen sprite to eliminate flicker during render
-    TFT_eSprite spr = TFT_eSprite(&tft);
-    spr.createSprite(DISPLAY_W, DISPLAY_H);
-    spr.fillSprite(COL_BG_LOGO);
-
-    // Triple gold outer ring
-    spr.drawCircle(DISPLAY_CX, DISPLAY_CY, DISPLAY_RADIUS,     COL_LOGO_ACCENT);
-    spr.drawCircle(DISPLAY_CX, DISPLAY_CY, DISPLAY_RADIUS - 2, COL_LOGO_ACCENT);
-    spr.drawCircle(DISPLAY_CX, DISPLAY_CY, DISPLAY_RADIUS - 4, COL_LOGO_ACCENT);
-
-    // Inner separator ring
-    spr.drawCircle(DISPLAY_CX, DISPLAY_CY, 88, COL_LOGO_ACCENT);
-
-    // Horizontal separator lines (clipped inside outer ring)
-    spr.drawFastHLine(DISPLAY_CX - 82, DISPLAY_CY - 25, 164, COL_LOGO_ACCENT);
-    spr.drawFastHLine(DISPLAY_CX - 82, DISPLAY_CY + 30, 164, COL_LOGO_ACCENT);
-
-    // Primary brand text
-    spr.setTextDatum(MC_DATUM);
-    spr.setTextColor(COL_LOGO_ACCENT, COL_BG_LOGO);
-    spr.drawString("MMC", DISPLAY_CX, DISPLAY_CY - 50, 6);
-
-    // Sub-label text
-    spr.setTextColor(TFT_WHITE, COL_BG_LOGO);
-    spr.drawString("RACK", DISPLAY_CX, DISPLAY_CY + 3, 4);
-    spr.drawString("SYSTEM", DISPLAY_CX, DISPLAY_CY + 33, 4);
-
-    spr.pushSprite(0, 0);
-    spr.deleteSprite();
+    static int sGreen[LOGO_N][2];
+    static int sBlue[LOGO_N][2];
+    static bool scaled = false;
+
+    if (!scaled) {
+        buildScaledLogo(sGreen, sBlue);
+        scaled = true;
+    }
+
+    tft.fillScreen(TFT_BLACK);
+
+    // Phase 1: green fade-in
+    for (int i = 0; i <= LOGO_FADE_STEPS; i++) {
+        fillPolygon(tft, sGreen, LOGO_N, logoGreenAt(i / (float)LOGO_FADE_STEPS));
+        delay(LOGO_FADE_STEP_MS);
+    }
+
+    // Phase 2: blue fade-in (green redrawn solid to prevent bleed-through)
+    for (int i = 0; i <= LOGO_FADE_STEPS; i++) {
+        fillPolygon(tft, sGreen, LOGO_N, logoGreenAt(1.0f));
+        fillPolygon(tft, sBlue,  LOGO_N, logoBlueAt(i / (float)LOGO_FADE_STEPS));
+        delay(LOGO_FADE_STEP_MS);
+    }
+
+    // Phase 3: static hold
+    delay(LOGO_STATIC_MS);
 }

+ 3 - 2
src/main.ino

@@ -9,6 +9,8 @@ static bool lastSelectState = false;
 // Show the correct screen for the current input state
 // ---------------------------------------------------------------------------
 static void applyState(bool sysSelect) {
+    // HIGH = System A
+    // LOW  = System B
     if (sysSelect) {
         displayShowSystemA(tft);
     } else {
@@ -26,8 +28,7 @@ void setup() {
     digitalWrite(PIN_TFT_BL, HIGH);
 
     displayInit(tft);
-    displayShowLogo(tft);
-    delay(LOGO_HOLD_MS);
+    displayShowLogo(tft); // animation handles its own 10 s timing
 
     lastSelectState = digitalRead(PIN_SYS_SELECT);
     applyState(lastSelectState);