How to display image from SD

Hello,
I am calling your help because i can’t find how to do that.

I have an ESP32 with a screen.
I want to load an image from the SD.

I’ve tried plenty of code but I think I don’t understand how to do that.
Did someone give me a full code to simply load the picture ?

Full Code

#include "Arduino.h"
#include "Audio.h"
#include "FS.h"
#include "SPI.h"
#include "SD.h"
#include <Wire.h>
#include <vector>
#include <lvgl.h>
#include <ui.h>
#include <Arduino_GFX_Library.h>

#define TFT_BL 2

#define SD_SCK 12 // CLK
#define SD_MISO 13 // D0
#define SD_MOSI 11 // CMD
#define SD_CS 10 // CLK

#define I2S_DOUT 17
#define I2S_BCLK 0
#define I2S_LRC 18

#define GFX_BL DF_GFX_BL // default backlight pin, you may replace DF_GFX_BL to actual backlight pin

#if defined(DISPLAY_DEV_KIT)
Arduino_GFX *gfx = create_default_Arduino_GFX();
#else /* !defined(DISPLAY_DEV_KIT) */

Arduino_ESP32RGBPanel *bus = new Arduino_ESP32RGBPanel(
    GFX_NOT_DEFINED /* CS */, GFX_NOT_DEFINED /* SCK */, GFX_NOT_DEFINED /* SDA */,
    40 /* DE */, 41 /* VSYNC */, 39 /* HSYNC */, 42 /* PCLK */,
    45 /* R0 */, 48 /* R1 */, 47 /* R2 */, 21 /* R3 */, 14 /* R4 */,
    5 /* G0 */, 6 /* G1 */, 7 /* G2 */, 15 /* G3 */, 16 /* G4 */, 4 /* G5 */,
    8 /* B0 */, 3 /* B1 */, 46 /* B2 */, 9 /* B3 */, 1 /* B4 */
);
// option 1:
// ST7262 IPS LCD 800x480
 Arduino_RPi_DPI_RGBPanel *gfx = new Arduino_RPi_DPI_RGBPanel(
   bus,
   800 /* width */, 0 /* hsync_polarity */, 8 /* hsync_front_porch */, 4 /* hsync_pulse_width */, 8 /* hsync_back_porch */,
   480 /* height */, 0 /* vsync_polarity */, 8 /* vsync_front_porch */, 4 /* vsync_pulse_width */, 8 /* vsync_back_porch */,
   1 /* pclk_active_neg */, 13000000 /* prefer_speed */, true /* auto_flush */);
#endif /* !defined(DISPLAY_DEV_KIT) */
/*******************************************************************************
 * End of Arduino_GFX setting
 ******************************************************************************/

/*******************************************************************************
 * Please config the touch panel in touch.h
 ******************************************************************************/
#include "touch.h"

Audio audio;
File musical;
std::vector<String> mp3Files;
int currentSongIndex = -1;
bool isPlaying = false;
bool playbackFinished = false; // Flag to detect playback completion

struct Music_info
{
    String name;
    int length;
    int runtime;
    int volume;
    int status;
    int mute_volume;
} music_info = {"", 0, 0, 0, 0, 0};

// Structure for storing file information
struct FileInfo
{
    String parentFolder;
    String currentFolder;
    String shortFileName;
    String fullPath;
};

std::vector<FileInfo> fileInfoList; // Store file information

// Structure for storing folder information
struct FolderInfo
{
    String parentFolder;
    String currentFolder;
    std::vector<FileInfo> files;
};

std::vector<FolderInfo> folderInfoList; // Store folder information

/* Change to your screen resolution */
static uint32_t screenWidth;
static uint32_t screenHeight;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t *disp_draw_buf;
static lv_disp_drv_t disp_drv;

// Add a variable to keep track of the current folder
String currentFolder = "/m"; // Start with the root folder

void setup()
{
    // Hardware initialization
    pin_init();
    sd_init();
    lv_init();
 
    // Init Display
    gfx->begin();

    gfx->fillScreen(RED);
    delay(500);
    gfx->fillScreen(GREEN);
    delay(500);
    gfx->fillScreen(BLUE);
    delay(500);
    gfx->fillScreen(BLACK);
    delay(500);
    lv_init();
    delay(10);
    touch_init();
    screenWidth = gfx->width();
    screenHeight = gfx->height();
#ifdef ESP32
    disp_draw_buf = (lv_color_t *)heap_caps_malloc(sizeof(lv_color_t) * screenWidth * screenHeight / 4, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
#else
    disp_draw_buf = (lv_color_t *)malloc(sizeof(lv_color_t) * screenWidth * screenHeight / 4);
#endif
    if (!disp_draw_buf)
    {
        Serial.println("LVGL disp_draw_buf allocate failed!");
    }
    else
    {
        lv_disp_draw_buf_init(&draw_buf, disp_draw_buf, NULL, screenWidth * screenHeight / 4);

        /* Initialize the display */
        lv_disp_drv_init(&disp_drv);
        /* Change the following line to your display resolution */
        disp_drv.hor_res = screenWidth;
        disp_drv.ver_res = screenHeight;
        disp_drv.flush_cb = my_disp_flush;
        disp_drv.draw_buf = &draw_buf;
        lv_disp_drv_register(&disp_drv);

        /* Initialize the (dummy) input device driver */
        static lv_indev_drv_t indev_drv;
        lv_indev_drv_init(&indev_drv);
        indev_drv.type = LV_INDEV_TYPE_POINTER;
        indev_drv.read_cb = my_touchpad_read;
        lv_indev_drv_register(&indev_drv);

        // Loading interface
        ui_init();

        lv_obj_add_event_cb(ui_btPrevious, playPreviousSong, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btPlay, playNextSong, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btPause, tooglePlayPause, LV_EVENT_PRESSED, NULL);        
        lv_obj_add_event_cb(ui_btNext, playNextSong, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btStop, stopSong, LV_EVENT_PRESSED, NULL);
        //lv_obj_add_event_cb(ui_btHome, ui_event_btHome, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btUp, goToPreviousFolder, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btDown, goToNextFolder, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btHistory, ui_event_btHistory, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_btMusical, setMusicalMode, LV_EVENT_PRESSED, NULL);
        lv_obj_add_event_cb(ui_sliderVolume, changeVolume, LV_EVENT_VALUE_CHANGED, NULL);

        // Setup MP3
        audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
        audio.setVolume(7); // Adjust the initial volume as needed

    // Set the root directory to "/m"
    //setRootDirectory(currentFolder); // Set the root directory to the current folder
    //printFolderInfoList();
    // Add more debugging statements
    //Serial.print("After setRootDirectory: ");
    //Serial.println(fileInfoList.size());

    // Play the first MP3 file if available
    //playFirstMP3IfAvailable();

    // Add a delay to ensure serial output is visible before the loop starts
    displayPngImage();
    //delay(1000); // You can adjust the delay time as needed
    }
}

void loop()
{
    // Your other main loop code can go here
    audio.loop();

    lv_event_t lv_event;
    checkAndPlayNextSong(&lv_event);

    // Check if playback is finished
    if (playbackFinished && isPlaying)
    {
        isPlaying = false;
        playbackFinished = false; // Reset the flag
    }

    lv_timer_handler(); /* let the GUI do its work */
    // Load and display the "Disney.bin" image
    //displayPngImage();
    delay(2);
}

// Functions
/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1);

#if (LV_COLOR_16_SWAP != 0)
    gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#else
    gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#endif

    lv_disp_flush_ready(disp);
}

void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
    if (touch_has_signal())
    {
        if (touch_touched())
        {
            data->state = LV_INDEV_STATE_PR;

            /*Set the coordinates*/
            data->point.x = touch_last_x;
            data->point.y = touch_last_y;
        }
        else if (touch_released())
        {
            data->state = LV_INDEV_STATE_REL;
        }
    }
    else
    {
        data->state = LV_INDEV_STATE_REL;
    }
}

// Function to play an MP3 file
void setMusicalMode(lv_event_t *e)
{
    currentFolder = "/m"; // Set the current folder to /m
    setRootDirectory(currentFolder); // Set the root directory to /m
    playFirstMP3IfAvailable(); // Play the first MP3 file in the new folder if available
}

void playFirstMP3IfAvailable()
{
    if (!fileInfoList.empty())
    {
        currentSongIndex = 0; // Start with the first MP3 file
        const String &firstMP3 = fileInfoList[currentSongIndex].fullPath;
        currentFolder = fileInfoList[currentSongIndex].currentFolder; // Update currentFolder
        playMP3(firstMP3);
    }
}

void playMP3(const String &mp3FilePath)
{
    const char *mp3Path = mp3FilePath.c_str();
    audio.connecttoSD(mp3Path);
    updateTitleLabel();
    isPlaying = true;
    playbackFinished = false; // Reset the flag
}

void checkAndPlayNextSong(lv_event_t *event)
{
    if (isPlaying && !audio.isRunning())
    {
        // Current MP3 file has finished playing, play the next one if available
        currentSongIndex++;
        if (currentSongIndex < fileInfoList.size())
        {
            const String &nextMP3 = fileInfoList[currentSongIndex].fullPath;
            playMP3(nextMP3);
        }
        else
        {
            // All MP3 files in the folder have been played
            isPlaying = false;
        }
    }
}

void playNextSong(lv_event_t *event)
{
    if (currentSongIndex >= 0 && currentSongIndex < fileInfoList.size() - 1)
    {
        currentSongIndex++;
        const String &nextMP3 = fileInfoList[currentSongIndex].fullPath;
        playMP3(nextMP3);
    }
    else if (!fileInfoList.empty())
    {
        // At the last song, play the first song of the folder
        currentSongIndex = 0;
        const String &firstMP3 = fileInfoList[currentSongIndex].fullPath;
        playMP3(firstMP3);
    }
}

void playPreviousSong(lv_event_t *event)
{
    if (currentSongIndex > 0)
    {
        currentSongIndex--;
        const String &prevMP3 = fileInfoList[currentSongIndex].fullPath;
        playMP3(prevMP3);
    }
    else
    {
        // Already at the first song, play it again or handle as needed
        const String &firstMP3 = fileInfoList[0].fullPath;
        playMP3(firstMP3);
    }
}

void replayCurrentSong()
{
    if (currentSongIndex >= 0 && currentSongIndex < fileInfoList.size())
    {
        const String &currentMP3 = fileInfoList[currentSongIndex].fullPath;
        playMP3(currentMP3);
        isPlaying = true;
    }
    else
    {
        // No song is currently playing, so start playing the first one
        playFirstMP3IfAvailable();
    }
}

void stopSong(lv_event_t *event) {
  audio.stopSong();
  isPlaying = false;
}

void tooglePlayPause(lv_event_t *event) {
    audio.pauseResume();

    if (isPlaying) {
      isPlaying = false;
    } else {
      isPlaying = true;
    } 
}

void updateTitleLabel() {
    if (currentSongIndex >= 0 && currentSongIndex < fileInfoList.size()) {
        String album = currentFolder; // Use the current folder as the album name
        int lastSlashIndex = album.lastIndexOf('/');
        if (lastSlashIndex != -1) {
            album = album.substring(lastSlashIndex + 1);
        }
        String title = fileInfoList[currentSongIndex].shortFileName;
        lv_label_set_text(ui_labelAlbum, album.c_str());
        lv_label_set_text(ui_labelTitle, title.c_str());
    } else {
        lv_label_set_text(ui_labelAlbum, "");
        lv_label_set_text(ui_labelTitle, "");
    }
}

void changeVolume(lv_event_t *event) {
    lv_event_code_t code = lv_event_get_code(event);
    
    if (code == LV_EVENT_VALUE_CHANGED) {
        int volume = lv_slider_get_value(ui_sliderVolume);
        audio.setVolume(volume);

        char volumeText[8];
        snprintf(volumeText, sizeof(volumeText), "%d", volume);
        lv_label_set_text(ui_labelVolume, volumeText);
    }
}

void goToPreviousFolder(lv_event_t *event)
{
    if (folderInfoList.empty()) {
        return;
    }

    int currentFolderIndex = -1;
    for (int i = 0; i < folderInfoList.size(); i++) {
        if (folderInfoList[i].currentFolder == currentFolder) {
            currentFolderIndex = i;
            break;
        }
    }

    if (currentFolderIndex <= 0) {
        currentFolderIndex = folderInfoList.size() - 1;
    } else {
        currentFolderIndex--;
    }

    currentFolder = folderInfoList[currentFolderIndex].currentFolder;
    fileInfoList = folderInfoList[currentFolderIndex].files;
    currentSongIndex = -1;


    playFirstMP3IfAvailable();
}

void goToNextFolder(lv_event_t *event)
{
    if (folderInfoList.empty()) {
        return;
    }

    int currentFolderIndex = -1;
    for (int i = 0; i < folderInfoList.size(); i++) {
        if (folderInfoList[i].currentFolder == currentFolder) {
            currentFolderIndex = i;
            break;
        }
    }

    if (currentFolderIndex == -1) {
        return;
    }

    if (currentFolderIndex == folderInfoList.size() - 1) {
        currentFolderIndex = 0;
        currentFolder = folderInfoList[currentFolderIndex].currentFolder;
        fileInfoList = folderInfoList[currentFolderIndex].files;
    } else {
        currentFolderIndex++;
        currentFolder = folderInfoList[currentFolderIndex].currentFolder;
        fileInfoList = folderInfoList[currentFolderIndex].files;
    }

    updateTitleLabel();
    currentSongIndex = -1;

    playFirstMP3IfAvailable();

    int currentIndex = currentFolderIndex;
}

void printFolderInfoList() {
    Serial.println("Folder Information List:");

    for (int i = 0; i < folderInfoList.size(); i++) {
        Serial.println("Folder Index: " + String(i));
        Serial.println("Parent Folder: " + folderInfoList[i].parentFolder);
        Serial.println("Current Folder: " + folderInfoList[i].currentFolder);
    }
}

void setRootDirectory(const String &rootDir)
{
    folderInfoList.clear();
    fileInfoList.clear();

    File root = SD.open(rootDir);
    if (!root)
    {
        Serial.println("Failed to open root directory.");
        return;
    }

    collectFolderInfo(root, "", rootDir);
    fileInfoList = folderInfoList[0].files; // Set fileInfoList based on the root folder

    root.close();
}

void collectFolderInfo(fs::File dir, String parentFolder, String currentFolder)
{
    FolderInfo folderInfo;
    folderInfo.parentFolder = parentFolder;
    folderInfo.currentFolder = currentFolder;

    while (File entry = dir.openNextFile())
    {
        String entryName = entry.name();

        if (entry.isDirectory())
        {
            String subfolder = currentFolder + "/" + entryName;
            Serial.print("Entering folder: ");
            Serial.println(subfolder);
            collectFolderInfo(entry, currentFolder, subfolder);
        }
        else
        {
            String fileExtension = entryName.substring(entryName.lastIndexOf('.') + 1);
            // Check if the file has an MP3 extension (you can add other audio file extensions if needed)
            if (fileExtension.equalsIgnoreCase("mp3"))
            {
                FileInfo fileInfo;
                fileInfo.parentFolder = parentFolder;
                fileInfo.currentFolder = currentFolder;
                fileInfo.shortFileName = entryName;
                fileInfo.fullPath = currentFolder + "/" + entryName;
                fileInfoList.push_back(fileInfo);
            }
        }

        entry.close();
    }

    if (currentFolder != "/m" && currentFolder != "/h") {
        folderInfo.files = fileInfoList;
        folderInfoList.push_back(folderInfo);
    }
    fileInfoList.clear();

    Serial.print("File count in ");
    Serial.print(currentFolder);
    Serial.print(": ");
    Serial.println(folderInfo.files.size());
}

void pin_init()
{
    pinMode(TFT_BL, OUTPUT);
    digitalWrite(TFT_BL, HIGH);
}

void sd_init()
{
    pinMode(SD_CS, OUTPUT);
    digitalWrite(SD_CS, HIGH);
    SPI.begin(SD_SCK, SD_MISO, SD_MOSI);
    SPI.setFrequency(400000);
    if (!SD.begin(SD_CS, SPI))
    {
        Serial.println("Card Mount Failed");
        while (1)
            ;
    }
    else
    {
        Serial.println("SD OK");
    }
}

void displayPngImage() {
lv_obj_t *img = lv_img_create(lv_scr_act());
//lv_img_set_src(img, "A:Disney.png"); // Use the correct file system letter and image file name
lv_img_set_src(img, "S:Disney.png");
}

How is your image WxH ? How is your lvconf png decode enabled?
How is your RAM config for png decode?

i’ve set this un lvconf.h but I don’t understand why should I do this while SquareLine Studio could display image with issue.

/*File system interfaces for common APIs */

/API for fopen, fread, etc/
#define LV_USE_FS_STDIO 1
#if LV_USE_FS_STDIO
#define LV_FS_STDIO_LETTER ‘S’ /Set an upper cased letter on which the drive will accessible (e.g. ‘A’)/
#define LV_FS_STDIO_PATH “/” /Set the working directory. File/directory paths will be appended to it./
#define LV_FS_STDIO_CACHE_SIZE 4096 />0 to cache this number of bytes in lv_fs_read()/
#endif

No one reply for my question…

I don’t understand the other questions, sorry

I’ve enabled this too :

/PNG decoder library/
#define LV_USE_PNG 1

Sorry but I find the documentation very unclear.
Instead of giving full clear example of what they are talking, they are parts of explications everywhere.

1 Like

WxH width and heigth off image and from docu you can read memory for this decode required is WxHx4

The image is small : 50x27

/**
 * Open a PNG image from a file and a variable
 */
void lv_example_png_1(void)
{
    lv_obj_t * img;

    img = lv_img_create(lv_scr_act());
    /* Assuming a File system is attached to letter 'A'
     * E.g. set LV_USE_FS_STDIO 'A' in lv_conf.h */
    lv_img_set_src(img, "A:lvgl/examples/libs/png/wink.png");
    lv_obj_align(img, LV_ALIGN_RIGHT_MID, -20, 0);
}

what isnt clear here ?

yes but it does not work
Maybe it’s about the A: I don’t know how to create the driver for the A.

Check my code in the first post, maybe you will find what is the issue.
For now, the image is called Disney.png and is in root of the sd card.

aghhh you use your path no example!

I know that I just set S:Disney.png or S:/Disney.png but no image.
I’ve tried with the wink.png on root of the SD, same issue, no image.

Simple you use audio class , that manage SD card , then lvgl have no access i mean. You require check and make coexist two SD systems…

hum I have no idea how to do that

When you require one little image place in flash as code , use online converter to raw for png, but this little size isnt most effective in png

What i want to do is to be able to switch images thru button.
It’s not important if pictures are in .c, .bin or else, I can convert them before but I read the doc and I can’t style be able to display anything (.c, bin etc)

Why is this so difficult to just display an image. SquareLine Studio is working nice, so why it does not work for my images.

When I am using LV_IMG_DECLARE the compiler is crashing with an error about gcc…

If you use Squareline dont waste time with …
One way is enable export images all, or better i use dummy screen and place here all required images. Then for show it in code only use set src call.

The goal is to be able to chose 3 images and then depending the image you chose, it will play a specific MP3. So it has to be dynamic.
I can’t display the image before clicking a button.

You use SQuareline or not ?

Yes I made an interface like this as test.
When in History mode (the button look as a Radio), the two arrow on right will be used to switch between image and validate is the play button, annulate is the stop button.

image

Then place Disney image and set it hiden…