Webserver serving an image in code

Hi there, I have a Feather S3 4MB no PSRAM and I cannot get SPIFFS to work... maybe it expects to be in the nonexistent PSRAM, but I digress.

I know that if I could get SPIFFS to work, then in both the ESP32 AsyncWebServer and ESP8266WebServer I could serve the files statically like this:

WebServer.serveStatic(uri.c_str(), SPIFFS, filename.c_str());

And finally, I know that I can probably try and change to LittleFS of FatFS...

I am still here because I don't want to do any of these things :slight_smile:

I have seen base64 encoded IMG tags, but this is not that either. Let's go with I want to serve my favicon.ico when the browser asks for it... which Chrome always does. So I want to register a handler like this:

WebServer.on(uri.c_str(), HTTP_GET, func);

and then in the func() I want to create the image and return it:

void serverResponse(int code, String type, String content) {
#ifdef ESP32
  if (__request) {
    __request->send(code, type, content);
  }
#else
  WebServer.send(code, type, content);
#endif
}

with code being 200, type being "image/png" for example, and content being the magic I want to create.

I saw somewhere that someone coded an image into a byte array... I think... and returned that... I can't remember, and I can't find any reference to it.

Does anyone have any ideas on how I can do this?

Thanks in advance.

Maybe this helps?

https://www.mischianti.org/images-to-byte-array-online-converter-cpp-arduino/

Thanks @jfjlaros, It wasn't quite what I was looking for, but it did inspire me.

I was looking for a script that created a byte-by-byte array of a file, rather than a raw image, but then I thought about how I would do it in PHP... I'd store a base64 encoded string and decode it on the fly. The S3 is powerful enough for this not to be a problem, so this works for me now.

First of all, find a BASE64 encoder and decoder. I struggled, but only, as it turns out, because Espresif has their own Base64.h file in the ESP32 code which is always included before libraries and sketch files which meant that installing a Base64 library in the library manager did not work. In the end I used this library by Arturo Guadalupi and just renamed the files XBase64.h and XBase64.cpp (and updated the include in the cpp file.

First of all, base64-encode the image. There are lots of "Convert PNG to BASE64" things on there net,

Store the output as a string, and then you can decode it on the fly

void showFavIcon(AsyncWebServerRequest * request) {
  __request = request;

  // Need the raw data, not the data URL...
  // "data:image/png;base64, iVBORw0KG...";
  char image[] PROGMEM = "iVBORw0KG...";

  int base64Len = strlen(image);
  int binaryLen = Base64.decodedLength(image, base64Len);

  char binary[binaryLen];
  Base64.decode(binary, image, base64Len);

  serverResponse(200, "image/png", binary, binaryLen);
}

void setup() {
...
  WebServer.on("/favicon.png", HTTP_GET, showFavIcon);
...
}

This did require me to write a version of the server response stuff that sent raw bytes back. That looks like this now:

void serverResponse(int code, String type, const char * content, size_t len) {
  if (__request) {

    // This only sends a code 200. The input code is redundant
    AsyncResponseStream *response = __request->beginResponseStream(type, len);
    for (size_t i = 0; i < len; i++) {
      response->print(content[i]);
    }
    __request->send(response);
  }
}

There is a HUGE caveat here (no pun intended), but the length of the string is quite important. I'm not sure of the limit, but my original favicon was 18kb. This made the S3 restart when stored as 24kb string. I eventually went for a 32x32 pixel image at 6Kb encoded.

Right, time for a snooze, as my head is hurting :slight_smile:

I hope that this helps someone, but after over a day of trying to get this to work for about 30 lines of code... I'd recommend trying to find out why SPIFFS doesn't work and then using that if you can. :slight_smile:

1 Like

If it works, it works.

When you run into memory issues, storing the data unencoded will save some flash space.

char const content[] PROGMEM = {0x00, 0x01, 0x02};

You can then avoid using RAM by accessing the flash data directly, e.g.,

response->write(pgm_read_byte(content + i));

Note that this is untested, but since AsyncResponseStream seems to inherit from Print, I figured it may work.

It's crashing there. I put serial output around it, and it gets to it, but not past it. I had it down to about 12kb but it still wasn't happy.... Hence 6k is good enough :slight_smile:

image should be a constant global or static variable for this to work.

If you want to define image outside of a function, you can use this.

char const image[] PROGMEM = ...

If you want to keep it where it is (inside of showFavIcon) the following should work.

static char const image[] PROGMEM = ...

Now that meant I could have a 64x64px PNG :slight_smile: The 128x128 was still a bit too big.

Thanks for all your help though, I've learned a lot.

A bit depending on the implementation of response->print(), you used to have the image in memory three times (once as a base64 encoded string (image), once as a decoded string (content) and once in the response object. You now have two copies remaining (in image and response).

If you follow the second suggestion in post #4, you can save an other copy by skipping the base64 encoding and writing the content directly to the response object.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.