From 061611f1643569d682c88cdb92e220e8f27662a1 Mon Sep 17 00:00:00 2001 From: Inter-Raptor Date: Thu, 9 Apr 2026 22:56:25 +0200 Subject: [PATCH 1/2] fix: prevent garbled launcher RAW images with explicit dimensions --- CarteSD/README_SD_IMAGES_LAUNCHER.txt | 27 +++++++++++ CarteSD/settings.json | 9 +++- README.md | 39 ++++++++++++++++ RaptorLauncher_V0.9/display_manager.cpp | 13 +++++- RaptorLauncher_V0.9/launcher_ui.cpp | 58 +++++++++++++++++++++++- RaptorLauncher_V0.9/settings_manager.cpp | 35 ++++++++++++++ RaptorLauncher_V0.9/settings_manager.h | 7 +++ 7 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 CarteSD/README_SD_IMAGES_LAUNCHER.txt diff --git a/CarteSD/README_SD_IMAGES_LAUNCHER.txt b/CarteSD/README_SD_IMAGES_LAUNCHER.txt new file mode 100644 index 0000000..e4ba85d --- /dev/null +++ b/CarteSD/README_SD_IMAGES_LAUNCHER.txt @@ -0,0 +1,27 @@ +RaptorLauncher v0.9 - Images du launcher (RAW) +=============================================== + +Placez les images a la racine de la carte SD (dossier CarteSD), pas dans /games : + +- /boot.raw -> image de lancement (affichee au demarrage) +- /home_bg.raw -> image de fond de l'accueil (icones + noms dessines par-dessus) + +Configuration dans /settings.json : + +{ + "boot_splash_raw": "/boot.raw", + "boot_splash_ms": 1500, + "boot_splash_w": 320, + "boot_splash_h": 240, + "home_bg_raw": "/home_bg.raw", + "home_bg_w": 320, + "home_bg_h": 240 +} + +Notes : +- boot_splash_ms est en millisecondes (ex: 1000 = 1 seconde, 2000 = 2 secondes). +- boot_splash_w / boot_splash_h et home_bg_w / home_bg_h doivent correspondre EXACTEMENT a la taille du fichier RAW. +- Si ton image est exportee en 240x320, mets 240x320 dans le settings (sinon l'image parait brouillee/cassee). +- Si boot.raw ou home_bg.raw n'existe pas (ou est invalide), le launcher reste en mode classique. +- Format attendu : RAW RGB565. +- Taille conseillee : 320x240 pour eviter recadrage/perte. diff --git a/CarteSD/settings.json b/CarteSD/settings.json index 7f0f16b..08b3ae8 100644 --- a/CarteSD/settings.json +++ b/CarteSD/settings.json @@ -1,6 +1,13 @@ { "volume": 30, "brightness": 25, + "boot_splash_raw": "/boot.raw", + "boot_splash_ms": 1500, + "boot_splash_w": 320, + "boot_splash_h": 240, + "home_bg_raw": "/home_bg.raw", + "home_bg_w": 320, + "home_bg_h": 240, "wifi_ssid": "VivienJardot", "wifi_pass": "VivienJardot1991", "touch_x_min": 317, @@ -11,4 +18,4 @@ "touch_offset_y": -2, "led_brightness": 5, "language": 1 -} \ No newline at end of file +} diff --git a/README.md b/README.md index c10e14e..d7879df 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,42 @@ ## Historique / corrigé Certaines remarques anciennes peuvent être partiellement obsolètes (ex. correctifs matériels/pins RGB déjà appliqués). Conserver ces points comme **historique**, et préciser l'état **corrigé/non corrigé** au fur et à mesure. + +## Vérification rapide — RaptorLauncher_V0.9 + +### Fond d'écran de l'accueil (derrière les icônes) +- Dans `RaptorLauncher_V0.9`, l'écran d'accueil efface d'abord l'écran avec `displayClear()`, puis dessine les icônes de jeux. +- Il n'y a pas de chargement d'image de fond globale pour la grille de jeux (pas de `home_bg` lu depuis la SD dans `drawHomeScreen()`). +- Résultat : en l'état, la v0.9 n'affiche pas de fond d'écran derrière les icônes sur la page qui liste les jeux. + +### Ordre d'affichage des jeux +- L'ordre est bien modifiable via `meta.json` de chaque jeu avec le champ `index` (ou `indice` en fallback). +- Le launcher trie d'abord par `index` croissant. +- En cas d'égalité d'`index`, il trie ensuite par nom (`name`) en ordre alphabétique. + +### Action pratique +- Pour changer l'ordre, modifier `index` dans `CarteSD/games//meta.json`, puis redémarrer le launcher. + +## Nouveaux réglages v0.9 (images RAW) + +Dans `settings.json` à la racine de la SD : + +```json +{ + "boot_splash_raw": "/boot.raw", + "boot_splash_ms": 1500, + "boot_splash_w": 320, + "boot_splash_h": 240, + "home_bg_raw": "/home_bg.raw", + "home_bg_w": 320, + "home_bg_h": 240 +} +``` + +- `boot_splash_raw` : image RAW affichée au démarrage (si absente/invalide = démarrage classique). +- `boot_splash_ms` : durée d'affichage au boot en millisecondes (0 à 10000). +- `boot_splash_w` / `boot_splash_h` : dimensions EXACTES du fichier RAW de splash. +- `home_bg_raw` : fond RAW de l'écran d'accueil (les icônes et noms des jeux sont dessinés par-dessus). +- `home_bg_w` / `home_bg_h` : dimensions EXACTES du fichier RAW du fond d'accueil. +- Le bouton **Paramètres** de l'accueil est remplacé par une icône **roue dentée** dessinée en code (pas chargée depuis la SD). +- Si les dimensions ne correspondent pas au fichier RAW, l'image peut apparaître brouillée/cassée. diff --git a/RaptorLauncher_V0.9/display_manager.cpp b/RaptorLauncher_V0.9/display_manager.cpp index 34bff17..0b917e5 100644 --- a/RaptorLauncher_V0.9/display_manager.cpp +++ b/RaptorLauncher_V0.9/display_manager.cpp @@ -215,6 +215,17 @@ bool displayDrawRAW(const char* path, int x, int y, int width, int height) { return false; } + size_t expectedSize = (size_t)width * (size_t)height * 2; + size_t fileSize = (size_t)rawFile.size(); + if (fileSize != expectedSize) { + Serial.print("[RAW] taille fichier invalide. attendu="); + Serial.print(expectedSize); + Serial.print(" octets, recu="); + Serial.println(fileSize); + rawFile.close(); + return false; + } + if (x >= lcd.width() || y >= lcd.height()) { Serial.println("[RAW] image hors ecran"); rawFile.close(); @@ -431,4 +442,4 @@ bool displayDrawBMPScaled(const char* path, int x, int y, int maxWidth, int maxH } return false; -} \ No newline at end of file +} diff --git a/RaptorLauncher_V0.9/launcher_ui.cpp b/RaptorLauncher_V0.9/launcher_ui.cpp index 06d6c78..751113e 100644 --- a/RaptorLauncher_V0.9/launcher_ui.cpp +++ b/RaptorLauncher_V0.9/launcher_ui.cpp @@ -218,6 +218,12 @@ static String resolveGamePath(const GameInfo& game, const String& path) { return String(SD_MOUNT_POINT) + game.folder + "/" + path; } +static String resolveSdPath(const String& path) { + if (path.length() == 0) return ""; + if (path.startsWith("/")) return path; + return String("/") + path; +} + enum TextKey { TK_SETTINGS, TK_VOLUME, TK_BRIGHTNESS, TK_AUDIO_TEST, TK_TOUCH_TEST, TK_TOUCH_CALIB, TK_LANGUAGE, TK_WIFI, TK_SSID, TK_LED, TK_OPEN, TK_TAP, TK_RESET_SETTINGS, TK_BACK, TK_SAVE, TK_CANCEL, @@ -344,6 +350,39 @@ static void drawFooterButton(int x, int y, const char* text) { displayDrawText(x, y + 8, text); } +static void drawSettingsGearButton(int x, int y) { + static const uint16_t GEAR16[16] = { + 0b0000000110000000, + 0b0000011111100000, + 0b0000111001110000, + 0b0001110000111000, + 0b0011100000011100, + 0b0011001111001100, + 0b0110011111100110, + 0b0110011001100110, + 0b0110011001100110, + 0b0110011111100110, + 0b0011001111001100, + 0b0011100000011100, + 0b0001110000111000, + 0b0000111001110000, + 0b0000011111100000, + 0b0000000110000000 + }; + + int iconX = x + 16; + int iconY = y + 8; + + for (int row = 0; row < 16; row++) { + for (int col = 0; col < 16; col++) { + bool on = (GEAR16[row] >> (15 - col)) & 0x1; + if (on) { + displayFillRect(iconX + col, iconY + row, 1, 1, COLOR_TEXT); + } + } + } +} + static void drawPlayButton(int x, int y, int w, int h, const char* text) { displayFillRect(x, y, w, h, COLOR_PLAY_BOX); displayDrawRect(x, y, w, h, COLOR_INFO_TEXT); @@ -469,6 +508,13 @@ static void launchGameFromIndex(int index) { static void drawHomeScreen() { displayClear(); + String bgPath = resolveSdPath(settingsGet().home_bg_raw); + if (bgPath.length() > 0) { + if (!displayDrawRAW(bgPath.c_str(), 0, 0, settingsGet().home_bg_w, settingsGet().home_bg_h)) { + Serial.println("[LAUNCHER] home_bg_raw absent/invalide -> fond classique"); + } + } + int pageCount = totalPages(); if (pageCount > 1) { @@ -518,7 +564,7 @@ static void drawHomeScreen() { } } - drawFooterButton(BTN_LEFT_X, BTN_LEFT_Y, tr(TK_PARAMETERS_BTN)); + drawSettingsGearButton(BTN_LEFT_X, BTN_LEFT_Y); if (pageCount > 1) { if (currentPage < pageCount - 1) { @@ -848,6 +894,16 @@ void launcherInit() { gWifiStatusText = ""; gLedSelectedColor = 0; ledManagerOff(); + + String splashPath = resolveSdPath(settingsGet().boot_splash_raw); + if (splashPath.length() > 0) { + if (displayDrawRAW(splashPath.c_str(), 0, 0, settingsGet().boot_splash_w, settingsGet().boot_splash_h)) { + delay(settingsGet().boot_splash_ms); + } else { + Serial.println("[LAUNCHER] boot_splash_raw absent/invalide -> demarrage classique"); + } + } + gNeedsRedraw = true; } diff --git a/RaptorLauncher_V0.9/settings_manager.cpp b/RaptorLauncher_V0.9/settings_manager.cpp index f000494..0503ac7 100644 --- a/RaptorLauncher_V0.9/settings_manager.cpp +++ b/RaptorLauncher_V0.9/settings_manager.cpp @@ -16,9 +16,16 @@ static int clampValue(int v, int minV, int maxV) { void settingsSetDefaults() { gSettings.volume = 80; gSettings.brightness = 100; + gSettings.boot_splash_ms = 1500; + gSettings.boot_splash_w = 320; + gSettings.boot_splash_h = 240; gSettings.wifi_ssid = ""; gSettings.wifi_pass = ""; + gSettings.boot_splash_raw = ""; + gSettings.home_bg_raw = ""; + gSettings.home_bg_w = 320; + gSettings.home_bg_h = 240; gSettings.touch_x_min = 200; gSettings.touch_x_max = 3800; @@ -38,8 +45,15 @@ bool settingsSave() { JsonDocument doc; doc["volume"] = gSettings.volume; doc["brightness"] = gSettings.brightness; + doc["boot_splash_ms"] = gSettings.boot_splash_ms; + doc["boot_splash_w"] = gSettings.boot_splash_w; + doc["boot_splash_h"] = gSettings.boot_splash_h; doc["wifi_ssid"] = gSettings.wifi_ssid; doc["wifi_pass"] = gSettings.wifi_pass; + doc["boot_splash_raw"] = gSettings.boot_splash_raw; + doc["home_bg_raw"] = gSettings.home_bg_raw; + doc["home_bg_w"] = gSettings.home_bg_w; + doc["home_bg_h"] = gSettings.home_bg_h; doc["touch_x_min"] = gSettings.touch_x_min; doc["touch_x_max"] = gSettings.touch_x_max; doc["touch_y_min"] = gSettings.touch_y_min; @@ -110,9 +124,16 @@ bool settingsInit() { gSettings.volume = clampValue(doc["volume"] | 80, 0, 100); gSettings.brightness = clampValue(doc["brightness"] | 100, 0, 100); + gSettings.boot_splash_ms = clampValue(doc["boot_splash_ms"] | 1500, 0, 10000); + gSettings.boot_splash_w = clampValue(doc["boot_splash_w"] | 320, 1, 1024); + gSettings.boot_splash_h = clampValue(doc["boot_splash_h"] | 240, 1, 1024); gSettings.wifi_ssid = doc["wifi_ssid"] | ""; gSettings.wifi_pass = doc["wifi_pass"] | ""; + gSettings.boot_splash_raw = doc["boot_splash_raw"] | ""; + gSettings.home_bg_raw = doc["home_bg_raw"] | ""; + gSettings.home_bg_w = clampValue(doc["home_bg_w"] | 320, 1, 1024); + gSettings.home_bg_h = clampValue(doc["home_bg_h"] | 240, 1, 1024); gSettings.touch_x_min = doc["touch_x_min"] | 200; gSettings.touch_x_max = doc["touch_x_max"] | 3800; @@ -128,9 +149,23 @@ bool settingsInit() { Serial.print("[SETTINGS] brightness = "); Serial.println(gSettings.brightness); + Serial.print("[SETTINGS] boot_splash_ms = "); + Serial.println(gSettings.boot_splash_ms); + Serial.print("[SETTINGS] boot_splash_w = "); + Serial.println(gSettings.boot_splash_w); + Serial.print("[SETTINGS] boot_splash_h = "); + Serial.println(gSettings.boot_splash_h); Serial.print("[SETTINGS] wifi_ssid = "); Serial.println(gSettings.wifi_ssid); + Serial.print("[SETTINGS] boot_splash_raw = "); + Serial.println(gSettings.boot_splash_raw); + Serial.print("[SETTINGS] home_bg_raw = "); + Serial.println(gSettings.home_bg_raw); + Serial.print("[SETTINGS] home_bg_w = "); + Serial.println(gSettings.home_bg_w); + Serial.print("[SETTINGS] home_bg_h = "); + Serial.println(gSettings.home_bg_h); Serial.print("[SETTINGS] touch_x_min = "); Serial.println(gSettings.touch_x_min); diff --git a/RaptorLauncher_V0.9/settings_manager.h b/RaptorLauncher_V0.9/settings_manager.h index 013bad7..ebbf3da 100644 --- a/RaptorLauncher_V0.9/settings_manager.h +++ b/RaptorLauncher_V0.9/settings_manager.h @@ -4,9 +4,16 @@ struct SystemSettings { int volume; // 0 à 100 int brightness; // 0 à 100 + int boot_splash_ms; // 0 à 10000 + int boot_splash_w; // largeur RAW splash + int boot_splash_h; // hauteur RAW splash String wifi_ssid; String wifi_pass; + String boot_splash_raw; // image RAW plein ecran au demarrage + String home_bg_raw; // image RAW fond ecran accueil + int home_bg_w; // largeur RAW fond accueil + int home_bg_h; // hauteur RAW fond accueil int touch_x_min; int touch_x_max; From 82fed0d7a8952bba4a1b09471473b712586f4d85 Mon Sep 17 00:00:00 2001 From: Inter-Raptor Date: Thu, 9 Apr 2026 23:00:35 +0200 Subject: [PATCH 2/2] fix: rework RAW rendering for splash/background with endian auto-detect --- CarteSD/README_SD_IMAGES_LAUNCHER.txt | 2 +- README.md | 1 + RaptorLauncher_V0.9/display_manager.cpp | 62 +++++++++++++++++++++++-- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/CarteSD/README_SD_IMAGES_LAUNCHER.txt b/CarteSD/README_SD_IMAGES_LAUNCHER.txt index e4ba85d..e6e74c0 100644 --- a/CarteSD/README_SD_IMAGES_LAUNCHER.txt +++ b/CarteSD/README_SD_IMAGES_LAUNCHER.txt @@ -23,5 +23,5 @@ Notes : - boot_splash_w / boot_splash_h et home_bg_w / home_bg_h doivent correspondre EXACTEMENT a la taille du fichier RAW. - Si ton image est exportee en 240x320, mets 240x320 dans le settings (sinon l'image parait brouillee/cassee). - Si boot.raw ou home_bg.raw n'existe pas (ou est invalide), le launcher reste en mode classique. -- Format attendu : RAW RGB565. +- Format attendu : RAW RGB565 (endianness LE/BE detectee automatiquement par le launcher). - Taille conseillee : 320x240 pour eviter recadrage/perte. diff --git a/README.md b/README.md index d7879df..1c101a2 100644 --- a/README.md +++ b/README.md @@ -90,3 +90,4 @@ Dans `settings.json` à la racine de la SD : - `home_bg_w` / `home_bg_h` : dimensions EXACTES du fichier RAW du fond d'accueil. - Le bouton **Paramètres** de l'accueil est remplacé par une icône **roue dentée** dessinée en code (pas chargée depuis la SD). - Si les dimensions ne correspondent pas au fichier RAW, l'image peut apparaître brouillée/cassée. +- L'affichage RAW détecte automatiquement l'endianness (LE/BE) pour éviter les couleurs corrompues. diff --git a/RaptorLauncher_V0.9/display_manager.cpp b/RaptorLauncher_V0.9/display_manager.cpp index 0b917e5..dd84d1a 100644 --- a/RaptorLauncher_V0.9/display_manager.cpp +++ b/RaptorLauncher_V0.9/display_manager.cpp @@ -84,6 +84,52 @@ static uint16_t bmpColorTo565(uint8_t r, uint8_t g, uint8_t b) { return lcd.color565(r, g, b); } +static inline int c565r(uint16_t c) { return (c >> 11) & 0x1F; } +static inline int c565g(uint16_t c) { return (c >> 5) & 0x3F; } +static inline int c565b(uint16_t c) { return c & 0x1F; } + +static bool detectRawLittleEndian(File& rawFile) { + const int SAMPLE_PIXELS = 256; + const int SAMPLE_BYTES = SAMPLE_PIXELS * 2; + + uint8_t sample[SAMPLE_BYTES]; + int n = rawFile.read(sample, SAMPLE_BYTES); + rawFile.seek(0); + + if (n < 8) { + return true; + } + + long scoreLE = 0; + long scoreBE = 0; + bool hasPrev = false; + uint16_t prevLE = 0; + uint16_t prevBE = 0; + + for (int i = 0; i + 1 < n; i += 2) { + uint8_t b0 = sample[i]; + uint8_t b1 = sample[i + 1]; + uint16_t pxLE = ((uint16_t)b1 << 8) | (uint16_t)b0; + uint16_t pxBE = ((uint16_t)b0 << 8) | (uint16_t)b1; + + if (hasPrev) { + scoreLE += abs(c565r(pxLE) - c565r(prevLE)); + scoreLE += abs(c565g(pxLE) - c565g(prevLE)); + scoreLE += abs(c565b(pxLE) - c565b(prevLE)); + + scoreBE += abs(c565r(pxBE) - c565r(prevBE)); + scoreBE += abs(c565g(pxBE) - c565g(prevBE)); + scoreBE += abs(c565b(pxBE) - c565b(prevBE)); + } + + prevLE = pxLE; + prevBE = pxBE; + hasPrev = true; + } + + return scoreLE <= scoreBE; +} + static bool readLE16(File& f, uint16_t& value) { uint8_t b0, b1; if (f.read(&b0, 1) != 1) return false; @@ -251,19 +297,27 @@ bool displayDrawRAW(const char* path, int x, int y, int width, int height) { return false; } - lcd.setSwapBytes(true); + bool littleEndian = detectRawLittleEndian(rawFile); + Serial.print("[RAW] endian detecte: "); + Serial.println(littleEndian ? "LE" : "BE"); + + lcd.setSwapBytes(false); for (int row = 0; row < drawHeight; row++) { for (int col = 0; col < drawWidth; col++) { - uint8_t hi, lo; - if (rawFile.read(&hi, 1) != 1 || rawFile.read(&lo, 1) != 1) { + uint8_t b0, b1; + if (rawFile.read(&b0, 1) != 1 || rawFile.read(&b1, 1) != 1) { Serial.println("[RAW] lecture pixel impossible"); lcd.setSwapBytes(false); free(lineBuffer); rawFile.close(); return false; } - lineBuffer[col] = ((uint16_t)hi << 8) | (uint16_t)lo; + if (littleEndian) { + lineBuffer[col] = ((uint16_t)b1 << 8) | (uint16_t)b0; + } else { + lineBuffer[col] = ((uint16_t)b0 << 8) | (uint16_t)b1; + } } if (width > drawWidth) {