Animated gifs with clock overlaid

Hello All

Im wanting to make a clock that has the time displayed while playing animated gifs at the same time behind the time maybe one every 30 seconds or so… Would this be difficult to achieve ? Some pointers on where to start would be very much appreciated if you have the time.

Thanks
Daniel

Hi Daniel,

Try looking at the code for the AnimatedGifs and MatrixClock examples in the SmartMatrix Library. You should be able to combine the two. You will need a background layer for playing GIFs, and an indexed color layer (right now that means a layer where each pixel is either 1 and set to a specific color, or the pixel is transparent so you can see the background layer behind). Hope that helps, let us know if you get stuck, or have something you want to share with others.

Thanks for the info, i was looking through the demo example that is there to get some ideas. Rite now its a little over my head understanding some of it… I have both examples running fine independently so i’ll see if i can get them to play together.
Hopefully i don’t bug you to much.

Thanks again
Daniel

I’d start with the AnimatedGIFs code, and import the MatrixClock example into AnimatedGIFs

Feel free to post back if you need more help

Hi Louis, thanks i started last night but im not sure if im on the rite track or not. Ive added what i think is needed up to the main loop. Could you have a look over the code and let me know if im getting there or if im just way off track please?
Im not really sure how to incorporate the clock loop into the gif loop so for now ive left that out…

`#include <SmartMatrix3.h>`
`    #include <SPI.h>`
`    #include <SD.h>`
  `  #include "GIFDecoder.h"`
 `   #include <Wire.h>`
 `   #include <Time.h>`
  `  #include <DS1307RTC.h>`

`#define DISPLAY_TIME_SECONDS 10`

`#define ENABLE_SCROLLING  1`

`// range 0-255`
`const int defaultBrightness = 30;`

`    const rgb24 COLOR_BLACK = {`
     `   0, 0, 0 };`

/* SmartMatrix configuration and memory allocation */
`#define COLOR_DEPTH 24                  // known working: 24, 48 - If the sketch uses type `rgb24` directly, COLOR_DEPTH must be 24`
`const uint8_t kMatrixWidth = 64;        // known working: 32, 64, 96, 128`
`const uint8_t kMatrixHeight = 64;       // known working: 16, 32, 48, 64`
`const uint8_t kRefreshDepth = 24;       // known working: 24, 36, 48`
`const uint8_t kDmaBufferRows = 2;       // known working: 2-4`
`const uint8_t kPanelType = SMARTMATRIX_HUB75_32ROW_MOD16SCAN; // use` `SMARTMATRIX_HUB75_16ROW_MOD8SCAN for common 16x32 panels`
`const uint8_t kMatrixOptions = (SMARTMATRIX_OPTIONS_NONE);    // see` `http://docs.pixelmatix.com/SmartMatrix for options`
`const uint8_t kBackgroundLayerOptions = (SM_BACKGROUND_OPTIONS_NONE);`
`const uint8_t kScrollingLayerOptions = (SM_SCROLLING_OPTIONS_NONE);`
`const uint8_t kIndexedLayerOptions = (SM_INDEXED_OPTIONS_NONE);`

`SMARTMATRIX_ALLOCATE_BUFFERS(matrix, kMatrixWidth, kMatrixHeight, kRefreshDepth,` `kDmaBufferRows, kPanelType, kMatrixOptions);`
`SMARTMATRIX_ALLOCATE_BACKGROUND_LAYER(backgroundLayer, kMatrixWidth,` `kMatrixHeight, COLOR_DEPTH, kBackgroundLayerOptions);`
`SMARTMATRIX_ALLOCATE_INDEXED_LAYER(indexedLayer, kMatrixWidth, kMatrixHeight, COLOR_DEPTH, kIndexedLayerOptions);`

    #if ENABLE_SCROLLING == 1
    SMARTMATRIX_ALLOCATE_SCROLLING_LAYER(scrollingLayer, kMatrixWidth, kMatrixHeight, COLOR_DEPTH, kScrollingLayerOptions);
    #endif

    // Chip select for SD card on the SmartMatrix Shield
    #define SD_CS 15

`#define GIF_DIRECTORY "/gifs/"`

`int num_files;`

    void screenClearCallback(void) {
      backgroundLayer.fillScreen({0,0,0});
    }

    void updateScreenCallback(void) {
      backgroundLayer.swapBuffers();
    }

    void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) {
      backgroundLayer.drawPixel(x, y, {red, green, blue});
    }
    const SM_RGB clockColor = {0xff, 0xff, 0xff};
    // Setup method runs once, when the sketch starts
    void setup() {
        setScreenClearCallback(screenClearCallback);
        setUpdateScreenCallback(updateScreenCallback);
        setDrawPixelCallback(drawPixelCallback);

        // Seed the random number generator
        randomSeed(analogRead(14));

  `  Serial.begin(115200);`

        // Initialize matrix
        matrix.addLayer(&indexedLayer); 
        matrix.addLayer(&backgroundLayer); 
    #if ENABLE_SCROLLING == 1
        matrix.addLayer(&scrollingLayer); 
    #endif

        matrix.begin();
      /* I2C Changes Needed for SmartMatrix Shield */
      // switch pins to use 16/17 for I2C instead of 18/19, after calling matrix.begin()//
      pinMode(18, INPUT);
      pinMode(19, INPUT);
      CORE_PIN16_CONFIG = (PORT_PCR_MUX(2) | PORT_PCR_PE | PORT_PCR_PS);
      CORE_PIN17_CONFIG = (PORT_PCR_MUX(2) | PORT_PCR_PE | PORT_PCR_PS);

       // display a simple message - will stay on the screen if calls to the RTC library fail later
      indexedLayer.fillScreen(0);
      indexedLayer.setFont(gohufont11b);
      indexedLayer.drawString(0, kMatrixHeight / 2 - 6, 1, "CLOCK");
      indexedLayer.swapBuffers(false);


        matrix.setRefreshRate(90);
        // for large panels, set the refresh rate lower to leave more CPU time to decoding GIFs (needed if GIFs are playing back slowly)

        // Clear screen
        backgroundLayer.fillScreen(COLOR_BLACK);
        backgroundLayer.swapBuffers();

// initialize the SD card at full speed
pinMode(SD_CS, OUTPUT);
if (!SD.begin(SD_CS)) {
`#if ENABLE_SCROLLING == 1`
    scrollingLayer.start("No SD card", -1);
`#endif`
    Serial.println("No SD card");
    while(1);
}

        // Determine how many animated GIF files exist
        num_files = enumerateGIFFiles(GIF_DIRECTORY, false);

if(num_files < 0) {
`#if ENABLE_SCROLLING == 1`
    scrollingLayer.start("No gifs directory", -1);
`#endif`
    Serial.println("No gifs directory");
    while(1);
}

>     if(!num_files) {
`> #if ENABLE_SCROLLING == 1`
>         scrollingLayer.start("Empty gifs directory", -1);
`> #endif`
>         Serial.println("Empty gifs directory");
>         while(1);
>     }
> }


`> void loop() {`



    
        matrix.setBrightness(defaultBrightness);
        unsigned long futureTime;
        char pathname[30];

  `  int index = random(num_files);`

        // Do forever
        while (true) {
            // Can clear screen for new animation here, but this might cause flicker with short animations
            // matrix.fillScreen(COLOR_BLACK);
            // matrix.swapBuffers();

            getGIFFilenameByIndex(GIF_DIRECTORY, index++, pathname);
            if (index >= num_files) {
                index = 0;
            }

    // Calculate time in the future to terminate animation
    futureTime = millis() + (DISPLAY_TIME_SECONDS * 1000);

    while (futureTime > millis()) {
        processGIFFile(pathname);
 `  int x = kMatrixWidth/2-15;`    
           
    }
}
}

When you paste your code, you can use the tool that looks like this </> to make a code block.

Put matrix.addLayer(&indexedLayer); after the background layer so it ends up on top. Right now it’s behind the background which isn’t transparent, and so it’s invisible.

If you’re interested in learning, I definitely encourage you to keep trying to create your own solution. If you just want something that works, Aurora can do this (among other things). Depending on the hardware you have, you might need to make slight changes to the code. It was designed for a 32x32, with an SD card reader, RTC, and infrared remote (and a MSGEQ7 if you want audio-reactive patterns).

I did see Aurora thanks but i would like to try and do it myself…

Thanks Louis i didnt realize the order of the layers, that fixed that issue. Hopefully you can help me with the below issue also…

I have the clock running on top of the GIFS while they play but i have a little issue that i just cant work out. On initial power-on the gifs play but the clock is not updated until three gifs have played and only then the correct time will be displayed. The clock is displayed but only ever gets updated always after three gifs have been played.

`  

#include <SmartMatrix3.h>
#include <SPI.h>
#include <SD.h>
#include "GIFDecoder.h"
#include <Wire.h>
#include <Time.h>
#include <DS1307RTC.h>

#define CLOCK              1
#define GIF              1


#define DISPLAY_TIME_SECONDS 1

#define ENABLE_SCROLLING  0

// range 0-255
const int defaultBrightness = 30;

const rgb24 COLOR_BLACK = {
0, 0, 0 };

/* SmartMatrix configuration and memory allocation */
#define COLOR_DEPTH 24
 
const uint8_t kMatrixWidth = 64;        // known working: 32, 64, 96, 128
const uint8_t kMatrixHeight = 64;       // known working: 16, 32, 48, 64
const uint8_t kRefreshDepth = 24;       // known working: 24, 36, 48
const uint8_t kDmaBufferRows = 2;       // known working: 2-4
const uint8_t kPanelType = SMARTMATRIX_HUB75_32ROW_MOD16SCAN; // use       SMARTMATRIX_HUB75_16ROW_MOD8SCAN for common 16x32 panels
const uint8_t kMatrixOptions = (SMARTMATRIX_OPTIONS_NONE);    // see   http://docs.pixelmatix.com/SmartMatrix for options
const uint8_t kBackgroundLayerOptions = (SM_BACKGROUND_OPTIONS_NONE);
const uint8_t kScrollingLayerOptions = (SM_SCROLLING_OPTIONS_NONE);
const uint8_t kIndexedLayerOptions = (SM_INDEXED_OPTIONS_NONE);

SMARTMATRIX_ALLOCATE_BUFFERS(matrix, kMatrixWidth, kMatrixHeight, kRefreshDepth,   kDmaBufferRows, kPanelType, kMatrixOptions);
SMARTMATRIX_ALLOCATE_BACKGROUND_LAYER(backgroundLayer, kMatrixWidth, kMatrixHeight,     COLOR_DEPTH, kBackgroundLayerOptions);
SMARTMATRIX_ALLOCATE_INDEXED_LAYER(indexedLayer, kMatrixWidth, kMatrixHeight,   COLOR_DEPTH, kIndexedLayerOptions);

#if ENABLE_SCROLLING == 1
SMARTMATRIX_ALLOCATE_SCROLLING_LAYER(scrollingLayer, kMatrixWidth, kMatrixHeight,     COLOR_DEPTH, kScrollingLayerOptions);
#endif

// Chip select for SD card on the SmartMatrix Shield
#define SD_CS 15

#define GIF_DIRECTORY "/gifs/"

int num_files;

void screenClearCallback(void) {
  backgroundLayer.fillScreen({0,0,0});
}

void updateScreenCallback(void) {
  backgroundLayer.swapBuffers();
}

void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) {
  backgroundLayer.drawPixel(x, y, {red, green, blue});
}
const SM_RGB clockColor = {0xff, 0xff, 0xff};
// Setup method runs once, when the sketch starts
void setup() {
 setScreenClearCallback(screenClearCallback);
setUpdateScreenCallback(updateScreenCallback);
setDrawPixelCallback(drawPixelCallback);

 // Seed the random number generator
randomSeed(analogRead(14));

Serial.begin(115200);

 // Initialize matrix

matrix.addLayer(&backgroundLayer);
matrix.addLayer(&indexedLayer); 

#if ENABLE_SCROLLING == 1
matrix.addLayer(&scrollingLayer); 
#endif

matrix.begin();
 /* I2C Changes Needed for SmartMatrix Shield */
 // switch pins to use 16/17 for I2C instead of 18/19, after calling matrix.begin()//
 pinMode(18, INPUT);
 pinMode(19, INPUT);
 CORE_PIN16_CONFIG = (PORT_PCR_MUX(2) | PORT_PCR_PE | PORT_PCR_PS);
 CORE_PIN17_CONFIG = (PORT_PCR_MUX(2) | PORT_PCR_PE | PORT_PCR_PS);

  // display a simple message - will stay on the screen if calls to the RTC library fail later
 indexedLayer.fillScreen(0);
 indexedLayer.setFont(gohufont11b);
 indexedLayer.drawString(0, kMatrixHeight / 2 - 6, 1, "   CLOCK");
 indexedLayer.swapBuffers(false);


 matrix.setRefreshRate(60);
// for large panels, set the refresh rate lower to leave more CPU time to decoding GIFs (needed if  GIFs        are playing back slowly)

  // Clear screen
backgroundLayer.fillScreen(COLOR_BLACK);
backgroundLayer.swapBuffers();

// initialize the SD card at full speed
pinMode(SD_CS, OUTPUT);
if (!SD.begin(SD_CS)) {
#if ENABLE_SCROLLING == 1
    scrollingLayer.start("No SD card", -1);
#endif
    Serial.println("No SD card");
    while(1);
 }

// Determine how many animated GIF files exist
num_files = enumerateGIFFiles(GIF_DIRECTORY, false);

  if(num_files < 0) {
 #if ENABLE_SCROLLING == 1
    scrollingLayer.start("No gifs directory", -1);
 #endif
    Serial.println("No gifs directory");
    while(1);
  }

  if(!num_files) {
  #if ENABLE_SCROLLING == 1
    scrollingLayer.start("Empty gifs directory", -1);
  #endif
    Serial.println("Empty gifs directory");
    while(1);
    }
  }



void loop() {

#if (CLOCK == 1)

Serial.println("Main loop start again");

tmElements_t tm;
int x = kMatrixWidth/2-15;
char timeBuffer[9];

// clear screen before writing new text
 indexedLayer.fillScreen(0);

 if  (RTC.read(tm)) {

Serial.print("Ok, Time = ");

print2digits(tm.Hour);

Serial.write(':');
print2digits(tm.Minute);

Serial.write(':');
print2digits(tm.Second);

Serial.print(", Date (D/M/Y) = ");
Serial.print(tm.Day);
Serial.write('/');
Serial.print(tm.Month);
Serial.write('/');
Serial.print(tmYearToCalendar(tm.Year));
Serial.println();




/* Draw Clock to SmartMatrix */
uint8_t hour = tm.Hour;
Serial.println("draw to matrix.");

if (hour > 12)
    hour -= 12;
sprintf(timeBuffer, "%d:%02d", hour, tm.Minute);
if (hour < 10)
    x += 3;
indexedLayer.setFont(gohufont11b);
indexedLayer.drawString(x, kMatrixHeight / 2 - 6, 1, timeBuffer);
indexedLayer.swapBuffers();
} else {
if (RTC.chipPresent()) {
  Serial.println("The DS1307 is stopped.  Please run the SetTime");
  Serial.println("example to initialize the time and begin running.");
  Serial.println();

  /* Draw Error Message to SmartMatrix */
  indexedLayer.setFont(font3x5);
  sprintf(timeBuffer, "Stopped");
  indexedLayer.drawString(x, kMatrixHeight / 2 - 3, 1, "Stopped");
 } else {
  Serial.println("DS1307 read error!  Please check the circuitry.");
  Serial.println();

  /* Draw Error Message to SmartMatrix */
  indexedLayer.setFont(font3x5);
  indexedLayer.drawString(x, kMatrixHeight / 2 - 3, 1, "No Clock");
 }
 indexedLayer.swapBuffers(false);
 delay(9000);
 }
 delay(1000);



 }


 void print2digits(int number) {
 if (number >= 0 && number < 10) {
 Serial.write('0');
}
Serial.print(number);






#endif




 #if (GIF == 1)
 { 
 matrix.setBrightness(defaultBrightness);
 unsigned long futureTime;
 char pathname[30];

 int index = random(num_files);

 

 // Do forever
 // while (true) {
    // Can clear screen for new animation here, but this might cause flicker with short animations
    // matrix.fillScreen(COLOR_BLACK);
    // matrix.swapBuffers();

    getGIFFilenameByIndex(GIF_DIRECTORY, index++, pathname);
    if (index >= num_files) {
        index = 0;
    }

    // Calculate time in the future to terminate animation
    futureTime = millis() + (DISPLAY_TIME_SECONDS * 1000);

    while (futureTime > millis()) {
        processGIFFile(pathname);
   int x = kMatrixWidth/2-15;    
           
    }


}

#endif

 }



    `

Thanks Daniel

Hi Daniel,

This might have to do with the way the AnimatedGifs processes GIF files. In most Arduino sketches, the loop() is called many times per second. In this sketch, when you call processGIFFile() it doesn’t return until the entire GIF has played. If you have a long GIF, there won’t be a pass through the loop again until the GIF has played, which might make your time updates slower than they should be.

I’m not sure why there are three GIFs playing before the clock updates again. Maybe add an else after the if (RTC.read(tm)) { block to see if this is failing every once in a while?

There are some large delays in the clock code e.g. delay(9000);. You might want to take those out. If you need to throttle the code so something happens every 9 seconds, read millis() and store it somewhere, then compare to the current time every pass through the loop to see if 9000 milliseconds have elapsed.

Hope that helps.

Gday Louis’

Thanks for the tips i’ll have to have a good hunt…

I would have thought on the first pass ( initial power on) that the time would be collected and updated but it never is, its always three gifs ( no matter the size) and then it collects the time and displays it… I think maybe like you say the read of the time is falling over… I can see in the serial link that part of the time is collected, it prints the hours on one pass the minutes on the next pass and so on…
I tried with and without the delays but it seemed to make no difference…

Ill keep hunting
Thanks again
Daniel

Try taking out the call to processGIF(), and add a long delay or wait for a keypress instead. See if it still takes a few passes to work.

Serial.println("\nPress Key For Next"); while(Serial.read() <= 0);

You added the GIF code inside print2digits(). There are three calls to print2digits() before the first call to indexedLayer.swapBuffers() which explains why three GIFs play before the time updates. I’d put the GIF code at the end of loop() instead.

Thanks Louis i had not seen that i had done that at all…Changed to loop and it now works as expected…

Now to modify a little to suit…

Thanks again

I made some major changes to the AnimatedGIFs sketch that may help with your project. The new version doesn’t block until the GIF finishes, you call a function once per frame. Details here:

Gday Louis’

Cool thanks for letting me know…

Ive added some buttons to change the britghtness/set time but had to disable the animated gif play while the changes were made to make the buttons usable.
Now that you have done this i wont have to disable playback while adjusting, ill check it out tonight and see if i can start using it. Funny i usually check to see if any updates in the smartmatrix git page but i must have missed this.

While i have you, im wanting to add a custom font for the clock, is that possible? would i just have to create the font and replace one of the existing fonts with this new one?

Funny i usually check to see if any updates in the smartmatrix git page but i must have missed this.

AnimatedGIFs has it’s own repo, which is included as a subrepo in the SmartMatrix Library repo. I’ll only update the subrepo in SmartMatrix Library when I’m doing a full release of the Library.

Regarding Fonts, documentation is sparse:

note on use: Added -s option to tweak output to match format needed for SmartMatri… · pixelmatix/bdf2c@b07deb7 · GitHub
e.g. ./bdf2c -s -n fontname -b < font.bdf > font.c

You don’t have to replace a font, but that’s probably an easy way to add a font you want to SmartMatrix. You can add a new one to the list, but all fonts are included in program memory as of now. It’s on my list to only link fonts used in a sketch.

Thanks Louis’

I added the new Animated Gif code you updated and all works well, enough time to make any adjustments (set time/ adjust brightness) between frames. The only thing i did notice is it is a little slower in playback of gifs than the previous code. I expect that is due to me using a 64x64 set up though.

Im looking for a way to not use futuretime and just play the whole gif from start to end before moving onto the next one, is that possible?

Looks like i have some reading to do on FONTS, with the 64x64 panel the existing FONTS are a little small so i want to make a larger custom FONT. Is there a limit on FONT size? Thanks for the info i’ll start reading…

Thanks again
Daniel

The callbacks that are used for getting data may make it slower. I haven’t spent any time optimizing, or comparing speed on a large frame. 64x64 is already pushing the limits of the Teensy, so any additional slowdown is probably noticeable.

Im looking for a way to not use futuretime and just play the whole gif from start to end before moving onto the next one, is that possible?

decodeFrame() returns an error code each time it’s called. It will return ERROR_DONE_PARSING when it has processed the last frame. You can use that to decide to start playing the next GIF.

Looks like fonts are limited to 8-pixels wide at the moment:

No problem, Ill have to make them 8 wide then, thanks

Not sure i understand how to use decodeFrame() ERROR_DONE_PARSING like you say, ill have to have another look through and try to work it out.

Thanks for the help Louis’

replace decodeFrame(); with if(decodeFrame() == ERROR_DONE_PARSING) { // do something after the last frame}