Display image in SD folder

Hello,

I would like to be able to display images (.bmp) that are located in a folder in an SD card.
The image is displayed on the screen when the image is not in any folder.

For now I am using this code:

#include <Adafruit_TFTLCD.h>
#include <MCUFRIEND_kbv.h>
#include <Adafruit_GFX.h>    // Core graphics library


 
#include <SPI.h>
#include <avr/pgmspace.h>
#include <SD.h>
#define SD_CS  10
 
const int XP=6,XM=A2,YP=A1,YM=7; //ID=0x9341
const int TS_LEFT=907,TS_RT=136,TS_TOP=942,TS_BOT=139;

uint16_t ID, oldcolor, currentcolor;
 
#define BLACK    0x0000
#define BLUE     0x001F
#define RED      0xF800
#define GREEN    0x07E0
#define CYAN     0x07FF
#define MAGENTA  0xF81F
#define YELLOW   0xFFE0
#define WHITE    0xFFFF
#define ORANGE   0xFBE0
 
MCUFRIEND_kbv tft;
 
int x, y;
char currentPage;
 
void setup() {
  Serial.begin(9600);
  uint16_t tmp;

    tft.reset();
    ID = tft.readID();
    tft.begin(ID);
    tft.setRotation(0);
  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CS)) {
    Serial.println("failed!");
    return;
  }
  Serial.println("OK!");
  tft.fillScreen(ORANGE);
  delay(1000);
 
 
}
 
void loop() {
 
Homescreen();
 
while(1);
}
 
void Homescreen(){
  bmpDraw("image.bmp",0,0);
  delay(5000); }
 
#define BUFFPIXEL 20
 
void bmpDraw(char *filename, int x, int y) {
 
  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel in buffer (R+G+B per pixel)
  uint16_t lcdbuffer[BUFFPIXEL];  // pixel out buffer (16-bit per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();
  uint8_t  lcdidx = 0;
  boolean  first = true;
 
  if((x >= tft.width()) || (y >= tft.height())) return;
 
  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');
  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.println(F("File not found"));
    return;
  }
 
  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.println(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
 
        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);
 
        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;
 
        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }
 
        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;
 
        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);
 
        for (row=0; row<h; row++) { // For each scanline...
          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }
 
          for (col=0; col<w; col++) { // For each column...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              // Push LCD buffer to the display first
              if(lcdidx > 0) {
                tft.pushColors(lcdbuffer, lcdidx, first);
                lcdidx = 0;
                first  = false;
              }
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }
 
            // Convert pixel from BMP to TFT format
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            lcdbuffer[lcdidx++] = tft.color565(r,g,b);
            
          } // end pixel
        } // end scanline
        // Write any remaining data to LCD
        if(lcdidx > 0) {
          tft.pushColors(lcdbuffer, lcdidx, first);
        }
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }
 
  bmpFile.close();
  if(!goodBmp) Serial.println(F("BMP format not recognized."));
}
 
// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.
 
uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}
 
uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

What to modify to fetch the images in a folder?

I have not used these libraries. Did you try to specify the entire path to the file, not just its name?

1 Like

It works with cam/img.bmp but not with a \.
Thank you, I didn't think it could work like that, I should have tried it myself!

You appear to be using MCUFRIEND_kbv library.

Please run the example sketches.

There are some sample .BMP files in C:\Users\ ... \Documents\Arduino\libraries\Mcufriend_kbv\extras\bitmaps

Copy them to the root folder on your SD.
Copy some to a specific your_folder on your SD.

Experiment with the example sketch. e.g. by specifying a particular folder
e.g. by matching a specific name pattern

Add more folders to your SD. Create more .BMP files.

David.

Yes by modifying that, I can scroll through the images in a folder.

char namebuf[32] = "/extras/bitmaps/";

But this code is not exactly what I wanted to do.
In my case we click to change the image.
I will now try to ensure that we don't have to type the name of the folder for each image (keeping the program from #1);

Go on. You can specify a full pathname.
You can just walk through a whole directory.
Or match a pattern.
Or you could display filenames that match a date.
Or treat as file numbers.
Or walk all the thousands of files on a whole SD.

If you give some examples of what you want, we could show how.

Imagine that I would have several "image groupings". In my SD card there will be a folder including the images for each "image grouping".
The home page refers to each "group of images" using 8 (or more or less) icons accompanied by a text.
When you click on an icon, the first image of the "image grouping" is displayed, to see the others you have to click on the right or on the left to make them appear.

In more concrete, for example.
I have 3 icons on my home page, one for Mont Blanc, one for Mount Everest, one for Mount McKinley.
I click on Mont Blanc and an image of the north face appears, I click on the right and an image from another point of view appears, etc.
There could be dozens of images.

Ex: I will search for the image there:
(SD/)montblanc/northface.bmp

And so for now I'd rather do something other than type this for each image:
bmpDraw("montblanc/northface.bmp",0,0);

This is what my code currently looks like (I'm at the stage of clicking left or right to change the image):

#include <Adafruit_TFTLCD.h>
#include <MCUFRIEND_kbv.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <TouchScreen.h>
#include "MCUFRIEND_kbv.h"
MCUFRIEND_kbv tft;

#define LOWFLASH (defined(__AVR_ATmega328P__) && defined(MCUFRIEND_KBV_H_))

#include <SPI.h>
#include <avr/pgmspace.h>
#include <SD.h>
#define SD_CS  10
 
const int XP=6,XM=A2,YP=A1,YM=7; //ID=0x9341
int TS_LEFT=959,TS_RT=184,TS_TOP=933,TS_BOT=184;

uint16_t ID, oldcolor, currentcolor;

int actual=0;
//char folders[] = "/pictures/"; ?????????????
 
#define BLACK    0x0000
#define BLUE     0x001F
#define RED      0xF800
#define GREEN    0x07E0
#define CYAN     0x07FF
#define MAGENTA  0xF81F
#define YELLOW   0xFFE0
#define WHITE    0xFFFF
#define ORANGE   0xFBE0
 

 
int x, y;
char currentPage;

TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
 
void setup() {
  Serial.begin(9600);
  uint16_t tmp;

    tft.reset();
    ID = tft.readID();
    tft.begin(ID);
    tft.setRotation(1);
  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CS)) {
    Serial.println("failed!");
    return;
  }
  Serial.println("OK!");
  tft.fillScreen(BLACK);
  bmpDraw("pictures/alpi.bmp",0,0);
 
}
 
void loop() {
  int pixel_x, pixel_y; 
  TSPoint p = ts.getPoint();
  if (p.z > ts.pressureThreshhold) {
    pixel_x = map(p.y, TS_LEFT=959, TS_RT=184, 0, 480);// mapping the values
    pixel_y = map(p.x, TS_TOP=184, TS_BOT=933, 0, 320);
    if(pixel_x>0 && pixel_x<80 && pixel_y>0 && pixel_y<320 ){//next
    pinMode(XM, OUTPUT);
    pinMode(YP, OUTPUT);
      if(actual==0){//img alpi
        bmpDraw("pictures/pania.bmp",0,0);
        actual=1;
      }
      else if(actual==1){//img pania
        bmpDraw("pictures/corchia.bmp",0,0);
        actual=2;
      }
      else if(actual==2){//img corchia
        bmpDraw("pictures/alpi.bmp",0,0);
        actual=0;
      }
    }
    else if(pixel_x>400 && pixel_x<480 && pixel_y>0 && pixel_y<320 ){//previous
    pinMode(XM, OUTPUT);
    pinMode(YP, OUTPUT);

    if(actual==0){//img alpi
        bmpDraw("pictures/corchia.bmp",0,0);
        actual=2;
      }
      else if(actual==1){//img pania
        bmpDraw("pictures/alpi.bmp",0,0);
        actual=0;
      }
      else if(actual==2){//img corchia
        bmpDraw("pictures/pania.bmp",0,0);
        actual=1;
      }
    
    }

    }
}
 
 
#define BUFFPIXEL 20
 
void bmpDraw(char *filename, int x, int y) {
 
  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel in buffer (R+G+B per pixel)
  uint16_t lcdbuffer[BUFFPIXEL];  // pixel out buffer (16-bit per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();
  uint8_t  lcdidx = 0;
  boolean  first = true;

 
  if((x >= tft.width()) || (y >= tft.height())) return;
 
  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.println('\'');
  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.println(F("File not found"));
    return;
  }
 
  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.println(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
 
        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);
 
        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;
 
        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }
 
        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;
 
        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);
 
        for (row=0; row<h; row++) { // For each scanline...
          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }
 
          for (col=0; col<w; col++) { // For each column...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              // Push LCD buffer to the display first
              if(lcdidx > 0) {
                tft.pushColors(lcdbuffer, lcdidx, first);
                lcdidx = 0;
                first  = false;
              }
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }
 
            // Convert pixel from BMP to TFT format
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            lcdbuffer[lcdidx++] = tft.color565(r,g,b);
            
          } // end pixel
        } // end scanline
        // Write any remaining data to LCD
        if(lcdidx > 0) {
          tft.pushColors(lcdbuffer, lcdidx, first);
        }
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }
 
  bmpFile.close();
  if(!goodBmp) Serial.println(F("BMP format not recognized."));
}
 
// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.
 
uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}
 
uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

To begin with, it would be nice not to have to type the pictures/ every time. It's probably very easy but I haven't found a solution yet.

Maybe if you look at these topics you will find a suitable idea.

I did something that seems simpler to me, it works fine but is it suitable for this use ?

I added this:

String newFileName = "default.bmp";
String name1 = "/pictures/";  //pour créer le nom du fichier
String name2 = ".bmp";

and

void bmpDraw(char *filename, int x, int y) {
newFileName=name1+filename+name2;

And I changed this:

if ((bmpFile = SD.open(newFileName)) == NULL) {

Now just type this for each image:
bmpDraw("corchia",0,0);

There are several ways to handle filenames.
e.g. change to a directory. access local files
e.g. construct absolute path /directory/filename

Store your pictures in different directories for each mountain.
e,g. Eiger, Everest, ...

A Uno has limited SRAM. I would avoid String methods.
It is easy to use an UP and DOWN button.
Not practical to have individual buttons for each file.
The Mcufriend Uno Shields use up most of your GPIO pins.

David.

Are you sure this works?

newFileName=name1+filename+name2;

See this link.

I still have plenty of room left in SRAM. So I can leave it like that for now. Unless we can do (almost) as simple with char?

I don't know if you've seen my entire code, but when I say click I mean tactile and I won't be using push buttons for this project.

Yes, it works perfectly. So much for making it simple? :grin:

Then maybe with buttons you can choose which mountain peak from which folder and cyclically call the pictures with a counter. For example, for Blanc you have String name1 = "/ Blanc /" ;, for Everest you have String name1 = "/ Everest /" and for McKinley you have String name1 = "/ McKinley /" ;. In all cases String name2 = ".bmp"; and between them you add a variable such as 'byte' type to get a file name full path like "/Everest/1.bmp" and so on. All you have to do is convert this counter 'byte' variable to String and 'add' it to the rest to get the full path with file name.

1 Like

It's a good idea !
That's what I'm going to do next.

I tested and we don't need to convert byte to String.
I have the impression that with the +, everything is converted to String.

  String newFileName = "default.bmp";
  String name1 = "/pictures/";  //pour créer le nom du fichier
  byte num=1;
  String name2= ".bmp";
  newFileName=name1+num+name2;

That works... :grinning:

With String it's very easy, that's why I keep it for now.
But since you said char is more suitable, I'll try to find a relatively easy way to do the same with char instead of String.

I don't think it's a problem to use Strings if you're careful. See the reserve() command for this purpose.

1 Like

Thanks !
So I keep them, and I'm going to read this.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.