mrcodetastic / ESP32-HUB75-MatrixPanel-DMA

An Adafruit GFX Compatible Library for the ESP32, ESP32-S2, ESP32-S3 to drive HUB75 LED matrix panels using DMA for high refresh rates. Supports panel chaining.
MIT License
899 stars 201 forks source link

Artifacts when playing a GIF file on a uniform background #631

Open Olejan opened 2 months ago

Olejan commented 2 months ago

I converted a Kinetic video from YouTube into a gif file with my screen size 128 * 64 (I used this service). I display this gif file on the screen as shown in the AnimatedGIFPanel_SPIFFS example. The video plays, but leaves a lot of artifacts. At first I thought the video was too dynamic and reduced the video playback speed. This did not give any result. Reducing fps didn't help either. At the same time, if you shoot a real video with a uniform background on camera and move, for example, your hand or black stick in it, then there will be no such effect. The video will play back without artifacts. As I understand it, these artifacts are caused by the fact that the gif file contains such a parameter as transparency.

I use four 64*32 modules connected according to the CHAIN_TOP_RIGHT_DOWN scheme, so I use VirtualMatrixPanel. If I don't use VirtualMatrixPanel, using only one panel out of four, then the video also plays with artifacts. I set the panel brightness very low - 20 units.

How to fix it?

I use ESP32-HUB75-MatrixPanel-DMA library version 3.0.10 My OS is Windows 10 64 bit

Kinetik gif: kinetic

Stick gif: stick

https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/assets/5707511/612c1d13-a93b-48d4-b342-7faa7f8d945f

https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/assets/5707511/18b03317-d7f9-4ecf-8c11-417d3eb9eca6

My code:

#include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
#define FILESYSTEM SPIFFS
#include <SPIFFS.h>
#include <AnimatedGIF.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>

#define EEPROM_BRIGHTNESS_ADDR  0

#include <EEPROM.h>
// ----------------------------

/*--------------------- MATRIX GPIO CONFIG  -------------------------*/
#define R1_PIN 33
#define G1_PIN 19
#define B1_PIN 26
#define R2_PIN 14
#define G2_PIN 5
#define B2_PIN 32
#define A_PIN 25
#define B_PIN 18 // Changed from library default
#define C_PIN 27
#define D_PIN 17
#define E_PIN -1//15 //-1 //16
#define LAT_PIN 4
#define OE_PIN 13
#define CLK_PIN 16

#define PIXEL_COLOR_DEPTH_BITS  4

#define PANEL_RES_X     64 // Number of pixels wide of each INDIVIDUAL panel module. 
#define PANEL_RES_Y     32 // Number of pixels tall of each INDIVIDUAL panel module.

#define NUM_ROWS        2 // Number of rows of chained INDIVIDUAL PANELS
#define NUM_COLS        2 // Number of INDIVIDUAL PANELS per ROW
#define PANEL_CHAIN     (NUM_ROWS * NUM_COLS)    // total number of panels chained one to another
#define VIRTUAL_MATRIX_CHAIN_TYPE CHAIN_TOP_RIGHT_DOWN

//MatrixPanel_I2S_DMA dma_display;
MatrixPanel_I2S_DMA *dma_display = nullptr;
// placeholder for the virtual display object
VirtualMatrixPanel* display = nullptr;

uint8_t _brightness = 6;

AnimatedGIF gif;
File f;

// Draw a line of image directly on the LED Matrix
void GIFDraw(GIFDRAW *pDraw)
{
    uint8_t *s;
    uint16_t *d, *usPalette, usTemp[640];//[320];
    int x, y, iWidth;

  iWidth = pDraw->iWidth;
  if (iWidth > MATRIX_WIDTH)
      iWidth = MATRIX_WIDTH;

  usPalette = pDraw->pPalette;
  y = pDraw->iY + pDraw->y; // current line

  s = pDraw->pPixels;
  if (pDraw->ucDisposalMethod == 2) // restore to background color
  {
    for (x=0; x<iWidth; x++)
    {
      if (s[x] == pDraw->ucTransparent)
          s[x] = pDraw->ucBackground;
    }
    pDraw->ucHasTransparency = 0;
  }
  // Apply the new pixels to the main image
  if (pDraw->ucHasTransparency) // if transparency used
  {
    uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
    int x, iCount;
    pEnd = s + pDraw->iWidth;
    x = 0;
    iCount = 0; // count non-transparent pixels
    while(x < pDraw->iWidth)
    {
      c = ucTransparent-1;
      d = usTemp;
      while (c != ucTransparent && s < pEnd)
      {
        c = *s++;
        if (c == ucTransparent) // done, stop
        {
          s--; // back up to treat it like transparent
        }
        else // opaque
        {
            *d++ = usPalette[c];
            iCount++;
        }
      } // while looking for opaque pixels
      if (iCount) // any opaque pixels?
      {
        for(int xOffset = 0; xOffset < iCount; xOffset++ ){
          display->drawPixel(x + xOffset, y, usTemp[xOffset]); // 565 Color Format
        }
        x += iCount;
        iCount = 0;
      }
      // no, look for a run of transparent pixels
      c = ucTransparent;
      while (c == ucTransparent && s < pEnd)
      {
        c = *s++;
        if (c == ucTransparent)
            iCount++;
        else
            s--; 
      }
      if (iCount)
      {
        x += iCount; // skip these
        iCount = 0;
      }
    }
  }
  else // does not have transparency
  {
    s = pDraw->pPixels;
    // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
    for (x=0; x<pDraw->iWidth; x++)
    {
      display->drawPixel(x, y, usPalette[*s++]); // color 565
    }
  }
} /* GIFDraw() */

void setBrightness(int br)
{
    _brightness = br;
    Serial.printf("Brightness: %d\n", _brightness);
    EEPROM.writeByte(EEPROM_BRIGHTNESS_ADDR, _brightness);
    EEPROM.commit();
    dma_display->setBrightness8(_brightness);
}

void * GIFOpenFile(const char *fname, int32_t *pSize)
{
  Serial.print("Playing gif: ");
  Serial.println(fname);
  f = FILESYSTEM.open(fname);
  if (f)
  {
    *pSize = f.size();
    return (void *)&f;
  }
  return NULL;
} /* GIFOpenFile() */

void GIFCloseFile(void *pHandle)
{
  File *f = static_cast<File *>(pHandle);
  if (f != NULL)
     f->close();
} /* GIFCloseFile() */

int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen)
{
    int32_t iBytesRead;
    iBytesRead = iLen;
    File *f = static_cast<File *>(pFile->fHandle);
    // Note: If you read a file all the way to the last byte, seek() stops working
    if ((pFile->iSize - pFile->iPos) < iLen)
       iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around
    if (iBytesRead <= 0)
       return 0;
    iBytesRead = (int32_t)f->read(pBuf, iBytesRead);
    pFile->iPos = f->position();
    return iBytesRead;
} /* GIFReadFile() */

int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition)
{ 
  int i = micros();
  File *f = static_cast<File *>(pFile->fHandle);
  f->seek(iPosition);
  pFile->iPos = (int32_t)f->position();
  i = micros() - i;
//  Serial.printf("Seek time = %d us\n", i);
  return pFile->iPos;
} /* GIFSeekFile() */

unsigned long start_tick = 0;

void ShowGIF(char *name)
{
  start_tick = millis();

  if (gif.open(name, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw))
  {
    Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
    Serial.flush();
    while (gif.playFrame(true, NULL))
    {      
      /*if ( (millis() - start_tick) > 8000) { // we'll get bored after about 8 seconds of the same looping gif
        break;
      }*/
    }
    gif.close();
  }

} /* ShowGIF() */

/*
* Установка параметров экрана:
* 'bxxx' - яркость экрана. 'b' - символ, обозначающий изменение яркости экрана, 'xxx' - значение яркости в диапазоне (0 - 255)
* 'dxxx' - задержка в мс между переключениями экранов в тестовой функции testPanel() для переменной _delay
*/
void setParameters()
{
    if (Serial.available())
    {
        String s = Serial.readString();
        Serial.printf("Got string: \"%s\"\n", s);
        if (s[0] == 'b')
        {
            s.remove(0, 1);
            setBrightness(s.toInt());
        }
        else if (s[0] == 'r')
        {
            ESP.restart();
        }
    }
}

void display_init()
{
    // Module configuration
    HUB75_I2S_CFG::i2s_pins _pins = { R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN };
    HUB75_I2S_CFG mxconfig(
        PANEL_RES_X, // Module width
        PANEL_RES_Y, // Module height
        PANEL_CHAIN, // chain length
        _pins // pin mapping
    );

    //mxconfig.gpio.e = 18;
    mxconfig.clkphase = false;
    mxconfig.driver = HUB75_I2S_CFG::FM6126A;
    mxconfig.latch_blanking = 1;
    mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_20M;

    // Display Setup
    dma_display = new MatrixPanel_I2S_DMA(mxconfig);
    _brightness = EEPROM.readByte(EEPROM_BRIGHTNESS_ADDR);
    Serial.printf("Read brightness: %d\n", _brightness);
    dma_display->setBrightness8(_brightness); //0-255
    dma_display->begin();

    display = new VirtualMatrixPanel((*dma_display), NUM_ROWS, NUM_COLS, PANEL_RES_X, PANEL_RES_Y, VIRTUAL_MATRIX_CHAIN_TYPE);
}

/************************* Arduino Sketch Setup and Loop() *******************************/
void setup() {
  Serial.begin(115200);

  EEPROM.begin(256);
  display_init();

  // Start filesystem
  Serial.println(" * Loading SPIFFS");
  if(!SPIFFS.begin()){
        Serial.println("SPIFFS Mount Failed");
  }

  /* all other pixel drawing functions can only be called after .begin() */
  dma_display->fillScreen(dma_display->color565(0, 0, 0));
  gif.begin(LITTLE_ENDIAN_PIXELS);

}

String gifDir = "/gifs"; // play all GIFs in this directory on the SD card
char filePath[256] = { 0 };
File root, gifFile;

void loop() 
{
  while (1) // run forever
  {
    root = FILESYSTEM.open(gifDir);
    if (root)
    {
      gifFile = root.openNextFile();
      while (gifFile)
      {
        if (!gifFile.isDirectory()) // play it
        {
          // C-strings... urghh...                
          memset(filePath, 0x0, sizeof(filePath));                
          strcpy(filePath, gifFile.path());

          // Show it.
          ShowGIF(filePath);
        }
        gifFile.close();
        gifFile = root.openNextFile();

        setParameters();
      }
      root.close();
    } // root
    delay(1000); // pause before restarting
  } // while
}
Lukaswnd commented 2 months ago

It seems like all new pixels of the gif are drawn, but not all of the old ones removes. This might be caused by a double buffer, which you do not have enabled, right?

Can you confirm transparency is disabled i? Simply add a print statement in the if (pDraw->ucHasTransparency) // if transparency used part of GIF_Draw.

If you see the print statement in your logs, try a differnt converter or try to disabe transparency in the converter

Olejan commented 2 months ago

This might be caused by a double buffer, which you do not have enabled, right?

I don't use double buffer. If I use mxconfig.double_buff = true; in the display_init function, then the image in the panel disappears completely. I set the parameter into false - the image appears.

Can you confirm transparency is disabled i? Simply add a print statement in the if (pDraw->ucHasTransparency) // if transparency used part of GIF_Draw.

Yes, my gif image use transparency

Olejan commented 2 months ago

I changed service of video to gif conversion, and the artifacts disappeared. But the file size has doubled. I am currently using this service.

But transparency is still present in the gif-file

Lukaswnd commented 2 months ago

I've been throught this my selfe last year, but I don't remeber exactily, but I think there is a bug in the transparent implementation of the AnimatedGIF library, thus some converters work, other don't and disabeling transparency in the convter helps. And I am very sure this is not a problem with is library, rather with AnimetedGIF

Olejan commented 2 months ago

@Lukaswnd Apparently so