Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Olejan opened this issue May 18, 2024 · 5 comments
Open

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

Olejan opened this issue May 18, 2024 · 5 comments
Labels
not an issue with library This library works as expected, but something else is the root cause, such as AdaFruitGFX

Comments

@Olejan
Copy link

Olejan commented May 18, 2024

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

kinetic.mp4
stick.mp4

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
Copy link
Contributor

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
Copy link
Author

Olejan commented May 22, 2024

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
Copy link
Author

Olejan commented May 22, 2024

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
Copy link
Contributor

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
Copy link
Author

Olejan commented May 27, 2024

@Lukaswnd Apparently so

@mrcodetastic mrcodetastic added the not an issue with library This library works as expected, but something else is the root cause, such as AdaFruitGFX label Aug 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
not an issue with library This library works as expected, but something else is the root cause, such as AdaFruitGFX
Projects
None yet
Development

No branches or pull requests

3 participants