bitbank2 / PNGdec

Arduino PNG image decoder library
Apache License 2.0
162 stars 25 forks source link

PNG.decode() returns 0, but PNG_DRAW_CALLBACK is never called #4

Closed han-fastolfe closed 2 years ago

han-fastolfe commented 2 years ago

I started from the png_transparency.ino sketch as a base, and I am trying to load a PNG from OpenWatherMap.org. The PNG data is opened and stored in a buffer

    snprintf(url, 256, "/img/wn/%s.png", doc["weather"][0]["icon"].as<String>().c_str());
    Serial.printf("Icon URL: %s\n", url);
    httpIconData = HttpClient(wifi, "openweathermap.org", 80);
    httpIconData.get(url);
    if (httpIconData.responseStatusCode() == 200)
    {
      Serial.println("Icon successfully retrieved");
      if (httpIconData.isResponseChunked() == true)
      {
      }
      else
      {
        unsigned int length = httpIconData.contentLength();
        Serial.printf("Icon size is %d\n Allocatibng memory.\n", length);
        pngData = (uint8_t *)malloc((size_t)length);
        httpIconData.responseBody().getBytes(pngData, length, 0);
        Serial.println("About to show icon");
        ShowPNG(pngData, length, dma_display, 0, 0);
        Serial.println("Freeing PNG data buffer");
        free(pngData);
      }
    }
    else
    {
      Serial.printf("Failed to retrieve icon: %d\n", httpWeatherData.responseStatusCode());
    }

Serial output:

Icon URL: /img/wn/02d.png
Icon successfully retrieved
Icon size is 852
 Allocatibng memory.
About to show icon

That buffer is then sent to the code derived from png_transparency.ino:

#include <PNGdec.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include "png.h"

typedef struct private_png_struct
{
    int16_t x_offset = 0;
    int16_t y_offset = 0;
    MatrixPanel_I2S_DMA *displayPanel = NULL;
    PNG *png = NULL;
} Private_PNG;

void PNGDraw(PNGDRAW *pDraw)
{
    Serial.println("About to draw PNG line");
    uint16_t usPixels[pDraw->iWidth];
    uint8_t ucMask[40];
    Private_PNG *pp = (Private_PNG *)pDraw->pUser;

    pp->png->getLineAsRGB565(pDraw, usPixels, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
    for (uint16_t x = 0; x < pDraw->iWidth; x++)
    {
        pp->displayPanel->drawPixel(x + pp->x_offset, pDraw->y + pp->y_offset, usPixels[x]);
        Serial.printf("(%d+%d, %d+%d): %0x%04x\n ", x, pp->x_offset, pDraw->y, pp->y_offset, usPixels[x]);
    }
}

PNG *ShowPNG(uint8_t *pngData, uint16_t length, MatrixPanel_I2S_DMA *display, int16_t x, int16_t y)
{
    PNG *png = new PNG();

    Serial.printf("Preparing to show icon of length %d\n", length);
    int rc = png->openRAM(pngData, length, PNGDraw);
    if (rc == PNG_SUCCESS)
    {
        Serial.println("Successfully opened icon from buffer");
        Private_PNG *pp = (Private_PNG *)malloc((size_t)sizeof(Private_PNG));
        if (x == -1)
        {
            pp->x_offset = (64 - png->getWidth()) / 2;
        }
        else
        {
            pp->x_offset = x;
        }
        if (y == -1)
        {
            pp->y_offset = (32 - png->getHeight()) / 2;
        }
        else
        {
            pp->y_offset = y;
        }
        Serial.printf("Offsets calculated: (%d, %d)\n", pp->x_offset, pp->y_offset);
        pp->displayPanel = display;
        pp->png = png;
        Serial.println("Private data set.");
        Serial.println("Drawing PNG now!");
        rc = png->decode((void *)pp, 0);
        Serial.printf("Draw result: %d\n", rc);
        free(pp);
        delete png;
    }
    return png;
}

Serial output:

Preparing to show icon of length 852
Successfully opened icon from buffer
Offsets calculated: (0, 0)
Private data set.
Drawing PNG now!
Draw result: 0
bitbank2 commented 2 years ago

Can you send me at least 1 example of a png file that you're not able to decode? With just your sketch code, it doesn't give me enough info to act on. Have you tried the sample sketches with the sample image data provided by my library? Did they properly decode the images?

han-fastolfe commented 2 years ago

Four examples are

http://openweathermap.org/img/w/01d.png http://openweathermap.org/img/wn/01d.png http://openweathermap.org/img/w/02d.png http://openweathermap.org/img/wn/02d.png

bitbank2 commented 2 years ago

I tested your files with the latest PNGdec and it works fine. Here it is running on my TinyS2 test rig. I didn't do anything special except not dynamically allocate the structures. That could be what's getting you in trouble.

20211219_150603

Also... those weather images are stored in a very wasteful pixel format. They don't have many unique colors, so reducing them to 8-bit palette (with transparency) reduces the file size to 1/10th of the original.

han-fastolfe commented 2 years ago

I turned everything into global variables, got rid of the dynamic allocation, even made the PNG data buffer a statically allocated array, but the PNGDraw() function still does not get called.

WiFiClient wifi;
HttpClient httpWeatherData = HttpClient(wifi, "api.openweathermap.org", 80);
HttpClient httpIconData = HttpClient(wifi, "openweathermap.org", 80);
StaticJsonDocument<400> doc;
StaticJsonDocument<200> filter;

char currentTemperature[11] = "-99C (-99)";
char maximumTemperature[5] = "-99C";
char minimumTemperature[5] = "-99C";
char humidity[5] = "100%";
char pressure[7] = "199kpa";
char url[32] = "";
uint8_t pngData[1024];

void getCurrentWeather()
{
  Serial.println("Trying to get data from OpenWeaterMap...");
  httpWeatherData.get("/data/2.5/weather?q=Kitchener,+CA&appid=**REDACTED**");
  if (httpWeatherData.responseStatusCode() == 200)
  {
    String response = httpWeatherData.responseBody();
    deserializeJson(doc, response, DeserializationOption::Filter(filter));
    serializeJsonPretty(doc, Serial);
    Serial.println();
    snprintf(currentTemperature, 11, "%-.fC (%-.f)", doc["main"]["temp"].as<double>() - 273.5, doc["main"]["feels_like"].as<double>() - 273.5);
    snprintf(maximumTemperature, 5, "%-.fC", doc["main"]["temp_max"].as<double>() - 273.5);
    snprintf(minimumTemperature, 5, "%-.fC", doc["main"]["temp_min"].as<double>() - 273.5);
    snprintf(humidity, 5, "%.f%%", doc["main"]["humidity"].as<double>());
    snprintf(pressure, 7, "%.fkPa", doc["main"]["pressure"].as<double>() / 10.0);
    snprintf(url, 32, "/img/wn/%s.png", doc["weather"][0]["icon"].as<String>().c_str());
    Serial.printf("Icon URL: %s\n", url);
    httpIconData.get(url);
    if (httpIconData.responseStatusCode() == 200)
    {
      Serial.println("Icon successfully retrieved");
      if (httpIconData.isResponseChunked() == true)
      {
      }
      else
      {
        unsigned int length = httpIconData.contentLength();
        Serial.printf("Icon size is %d\nAllocatibng memory.\n", length);
        httpIconData.responseBody().getBytes(pngData, length, 0);
        Serial.println("About to show icon");
        ShowPNG(pngData, length, dma_display, 0, 0);
        Serial.println("Freeing PNG data buffer");
      }
    }
    else
    {
      Serial.printf("Failed to retrieve icon: %d\n", httpWeatherData.responseStatusCode());
    }
  }
  else
  {
    Serial.println("Getting data from OpenWeatherMap failed!");
  }
}
#include <PNGdec.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include "png.h"

MatrixPanel_I2S_DMA *panel = NULL;
PNG png;
uint16_t png_x_offset, png_y_offset;

void PNGDraw(PNGDRAW *pDraw)
{
    Serial.println("About to draw PNG line");
    uint16_t usPixels[pDraw->iWidth];
    uint8_t ucMask[40];

    png.getLineAsRGB565(pDraw, usPixels, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
    for (uint16_t x = 0; x < pDraw->iWidth; x++)
    {
        panel->drawPixel(x + png_x_offset, pDraw->y + png_y_offset, usPixels[x]);
        Serial.printf("(%d+%d, %d+%d): %0x%04x\n ", x, png_x_offset, pDraw->y, png_y_offset, usPixels[x]);
    }
}

void ShowPNG(uint8_t *pngData, uint16_t length, MatrixPanel_I2S_DMA *display, int16_t x, int16_t y)
{

    Serial.printf("Preparing to show icon of length %d\n", length);
    panel = display;
    int rc = png.openRAM(pngData, length, PNGDraw);
    if (rc == PNG_SUCCESS)
    {
        Serial.println("Successfully opened icon from buffer");
        if (x == -1)
        {
            png_x_offset = (64 - png.getWidth()) / 2;
        }
        else
        {
            png_x_offset = x;
        }
        if (y == -1)
        {
            png_y_offset = (32 - png.getHeight()) / 2;
        }
        else
        {
            png_y_offset = y;
        }
        Serial.printf("Offsets calculated: (%d, %d)\n", png_x_offset, png_y_offset);
        Serial.println("Drawing PNG now!");
        rc = png.decode(NULL, 0);
        Serial.printf("Draw result: %d\n", rc);
    }
}
bitbank2 commented 2 years ago

The library is able to decode the images you provided and display them on a SPI LCD. I structured my sample app the same way as yours and pass it a custom structure to center the image. Start with a simpler example and work your way up. Your code has a lot going on with WiFi, openweather, etc. PNG decoding requires a lot of RAM. Even an ESP32 can have trouble with it if other parts of your code are using lots of RAM.