Como vimos en el post anterior pudimos ver el código para poder visualizar imágenes en nuestra pantalla TFT con Arduino.

tftfrontback-p

Ahora pasaré a explicar un método automatizado y rápido para crear nuestro book de fotos… Como esos marcos digitales que se llevan vendiendo y a casi nadie le gustan. Pero por un módico precio y de forma personalizada vamos a hacer de este sistema algo sencillo; pero hay que aprender algunos trucos.

Como ya hemos dicho, el primer ejemplo para visualizar una imagen vamos a acceder a través de la tarjeta SD.

Pero todo este proceso vamos a realizarlo con un conjunto grande de imágenes guardadas en una carpeta de nuestra tarjeta. Así que los pasos a realizar son los siguientes.

  • Conseguir un montón de imágenes
  • Cambiar el formato de imagen a .bmp de forma automática
  • Renombrar todas las imágenes con un orden específico
  • Introducir las imágenes en la tarjeta SD
  • Programar Arduino
  • Visualizar el resultado

Conseguir un montón de imágenes

El proceso para conseguir un montón de imágenes, si no disponemos aún de una carpeta, voy a mostrar un programa o utilidad que proporciona un modo rápido de conseguir una infinita fuente de imágnes que se pueden incluso buscar en categorías.

Aunque se pueden buscar muchas fuentes como buscadores de imágenes en navegador o páginas específicas, así como archivos recopilatorios. El objetivo es encontrar algún método que sepamos que la mayoría de fotos tienen relación entre ellas y con calidad.

Mi fuente en este caso es Tumblr o Instagram. Porque de estas plataformas se consigue una filtración de contenido que supera con creces otras búsquedas.

Utilizaremos un programa llamado TumblrRipper. Este es un software gratuito que realiza una búsqueda automatizada de cualquiera de las páginas que se especifiquen.

Por ejemplo, en función de la modalidad se puede encontrar:

Existen millones más, solo hay que buscar bien, pero recomiendo que no os pongáis a descargar una fuente con casi un millón de imágenes, porque podéis esperar un rato largo.

En este ejemplo voy a escoger una recopilación personal de animales, unas 3800 fotos que pueden tardar en descargarse una hora aproximadamente.

Siguiendo estos pasos y clicando en el botón final de RUN, veremos como empieza el proceso automáticamente e inserta todas las imágenes en la carpeta elegida.

 

Cambiar el formato de imagen a .bmp de forma automática

Para realizar este paso realizaremos un proceso automatizado con Photoshop. Photoshop nos permite declarar lo que se denominan Acciones, y aplicar estos cambios a todo un conjunto de fotos contenidos en una carpeta de manera automatizada.

Lo que vamos a hacer es redimensionar todas las fotos para que puedan aparecer en nuestra pantalla que es de 240×320 píxeles. El formato depende de cada foto, pero en este ejemplo haremos todas las fotos con un altura de 320 píxeles y Photoshop las redimensionará automaticamente.

Siguiendo los pasos de las imágenes que aparecen a continuación se puede ir siguiendo este proceso.

 

Una vez que presionemos el botón OK, se pondrá a realizar automaticamente el proceso para todas las imágenes y solo habrá que esperar.

Es conveniente estar pendiente del proceso, porque a veces se puede pausar debido a algún error de entrada. Como una imagen que no se puede abrir o leer, o cualquier otra cosa totalmente aleatoria. En principio si no hay ninguna ocurrencia se espera durante el proceso y si no dejamos a un patito que apriete el botón OK todo el rato.

drinking_bird_the_simpsons

Renombrar todas las imágenes con un orden específico

Una vez tengamos toda nuestra recopilación dentro de una carpeta, podremos ver que los nombres se asocian a los descargados de la página web escogida. Por lo que vamos a renombrar todos los archivos con un número indicativo al final para establecer un orden.

Para ello, lo primero que hemos de hacer, es acceder a la carpeta, seleccionar todos los ficheros; para hacer esto se puede mantener presionado el botón SHIFT y clicar sobre el primer y último archivo de la carpeta; y renombrarlos todos con el mismo nombre. Apretando el botón F2 y escribiendo un nombre para uno de los ficheros, veremos como el resto establece un número al final de cada uno de forma que quedan ordenados.

RenameAnimals RenameFilesPro

*ATENCION: Windows renombra con el mismo nombre distintos formatos. Por lo que es conveniente en pasos posteriores separar los archivos en carpetas con su formato .PNG, .JPG o .GIF.
O eso o renombrar todos los archivos con nombres diferentes independientemente del formato.

Quizás tengamos algún problema con espacios y paréntesis para poder accederlos a través de programación. Los nombres de los archivos a la hora de programar cuanto más sencillos mejor.

Para resolver este problema accederemos a PowerShell. PowerShell es una interfaz de consola para Windows desde el que podremos realizar tareas por lineas de comandos y facilitarnos mucho trabajo en este caso.

PowerShell

Desde esta linea de comandos, apareceremos en la carpeta de documentos y accederemos a nuestra carpeta de imágenes con el siguiente comando.


cd .\Pictures\Wonder_Creatures\

En mi caso se llama Wonder_Creatures como la página seleccionada, pero puede tener cualquier otro nombre, especificado en el paso anterior.

Ahora con el siguiente comando realizaremos un renombrado de todos los archivos de la carpeta, Convertiremos los espacios en blanco en una barra baja y eliminaremos los paréntesis.


Dir | Rename-Item –NewName { $_.name –replace “ “,”_” }


Dir | Rename-Item –NewName { $_.name –replace “\(“,”” }


Dir | Rename-Item –NewName { $_.name –replace “\)“,”” }

PowerShellRename

Ahora si listamos con ls, o accedemos a la carpeta podremos ver el resultado.

Introducir las imágenes en la tarjeta SD

Esto es quizás lo más fácil de el proceso, solo tenemos que copiar todas las imágenes en una carpeta a la SD.

IMPORTANTE!!

El nombre de la carpeta que contenga todas las imágenes que sea corto. De unas 8 letras como máximo, ya que a la hora de programar Arduino nos puede dar problemas de acceso a la carpeta. Yo en este ejemplo, llamaré a la carpeta “Animals” – 7 letras.

*Así no tenemos todas las imágenes desordenadas por todas partes.

Programar Arduino

 

Ahora programaremos la placa para visualizar el resultado en la pantalla TFT. Como habíamos dicho en un post anterior, son necesarias las siguientes librerías.

Una vez hecho esto habremos de copiar el código, no sin antes especificar muy bien en qué carpeta y cuál es el índice de las imágenes que se van a visualizar.

Esto es así porque nuestro acceso a las imágenes va a ser a través de un bucle que irá incrementado el índice que aparece en el nombre de la imagen para ser leida y presentarla en nuestra pantalla.

 

int FOLDERSIZE = 400

String FOLDERROOT = “Animals/”;
String FOLDERINDEX = “A_”;


#include <SPI.h>;
#include <SD.h>;

// *** SPFD5408 change -- Begin
#include <SPFD5408_Adafruit_GFX.h>    // Core graphics library
#include <SPFD5408_Adafruit_TFTLCD.h> // Hardware-specific library
#include <SPFD5408_TouchScreen.h>

// *** SPFD5408 change -- End

// The control pins for the LCD can be assigned to any digital or
// analog pins...but we'll use the analog pins as this allows us to
// double up the pins with the touch screen (see the TFT paint example).
#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0

#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin

// Assign human-readable names to some common 16-bit color values:
#define  BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

#define SD_CS 10

File myFile;

int FOLDERSIZE = 400;

String FOLDERROOT = "Animals/";
String FOLDERINDEX = "Animals_";

void setup() {
  Serial.begin(9600);
  uint16_t identifier = tft.readID();
  
  tft.begin(0x9341); // SDFP5408
  tft.fillScreen(WHITE);
  tft.setRotation(0);
  // see if the card is present and can be initialized:
  if (!SD.begin(SD_CS)) {
    
    progmemPrintln(PSTR("Card failed"));
    tft.println("Card Failed");
    return;
  }
  tft.setTextSize(3);
  tft.println("Card Ready");
  tft.setCursor(0,30);

  tft.fillScreen(0);
}

void loop() {
  for (int i = 1; i < FOLDERSIZE; i++ ){
    String filename = FOLDERROOT+ FOLDERINDEX + i+".bmp";
    int len = 30;
    char filecharname[len];
    filename.toCharArray(filecharname, len);
    Serial.println(filecharname);
    
    File dataFile = SD.open(filename);
    if (dataFile) {
      bmpDraw(filecharname , 0, 0);
    }else{
      tft.println("File Not Found");
    }
    delay(2000);
  }
}

El código completo es el siguiente.

*Para evitar problemas con el tamaño de los nombres, he renombrado todas las imágenes con una A y así evitamos el problema de la longitud del nombre.


#include <SPI.h>
#include <SD.h>

// *** SPFD5408 change -- Begin
#include <SPFD5408_Adafruit_GFX.h>    // Core graphics library
#include <SPFD5408_Adafruit_TFTLCD.h> // Hardware-specific library
#include <SPFD5408_TouchScreen.h>

// *** SPFD5408 change -- End

// The control pins for the LCD can be assigned to any digital or
// analog pins...but we'll use the analog pins as this allows us to
// double up the pins with the touch screen (see the TFT paint example).
#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0

#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin

// Assign human-readable names to some common 16-bit color values:
#define  BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

#define SD_CS 10

File myFile;

int FOLDERSIZE = 400;

String FOLDERROOT = "Animals/";
String FOLDERINDEX = "A_";

void setup() {
  Serial.begin(9600);
  uint16_t identifier = tft.readID();
  
  tft.begin(0x9341); // SDFP5408
  tft.fillScreen(WHITE);
  tft.setRotation(0);
  // see if the card is present and can be initialized:
  if (!SD.begin(SD_CS)) {
    
    progmemPrintln(PSTR("Card failed"));
    tft.println("Card Failed");
    return;
  }
  tft.setTextSize(3);
  tft.println("Card Ready");
  tft.setCursor(0,30);

  tft.fillScreen(0);
}

void loop() {
  for (int i = 1; i < FOLDERSIZE; i++ ){ 
     String filename = FOLDERROOT + FOLDERINDEX+ i+".bmp"; 
     int len = 30; 
     char filecharname[len]; 
     filename.toCharArray(filecharname, len); 
     Serial.println(filecharname); 
     File dataFile = SD.open(filename); 
     if (dataFile) { 
        bmpDraw(filecharname , 0, 0); 
     }else{ 
        tft.println("File Not Found"); 
     } 
     delay(2000); 
   } 
} 

#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();
  progmemPrint(PSTR("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');
  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    progmemPrintln(PSTR("File not found"));
    return;
  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    progmemPrint(PSTR("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    progmemPrint(PSTR("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    progmemPrint(PSTR("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
      progmemPrint(PSTR("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        progmemPrint(PSTR("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);
        } 
        progmemPrint(PSTR("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
  }

  bmpFile.close();
  if(!goodBmp) progmemPrintln(PSTR("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;
}
void progmemPrint(const char *str) {
  char c;
  while(c = pgm_read_byte(str++)) Serial.print(c);
}

void progmemPrintln(const char *str) {
  progmemPrint(str);
  Serial.println();
}




*ATENCION:
Cuando transcurren unas 15 fotos aproximadamente el programa deja de funcionar debido a u error de acceso. Por algún motivo el título del archivo se descompone y se reinicia el proceso.