Graphic display libraries - Print rather than sending to a display

I'm trying to create a virtual display, in which data is sent to a webserver as a 8bit gray scale .bmp (printable) image, rather than the display itself

Has anyone attempted such an implementation before?

Not that i know, but i'd use AdafruitGFX and go for 16-bit RGB for a format, or use modified parts of that library for 8-bit GS. Shouldn't be to hard to make it write to a memory location and then send the whole thing.

Thanks.
Yes, I'm planning on modifying one of their libraries, that way is super easy to just swap code, if using a real display, in particular the ILI9341. With a bit of luck I could fit it all in the RAM of an ESP8266. Wondering how much is free after the stack and the arduino stuff?

Depends a little on which ESP you are using.

Deva_Rishi:
Depends a little on which ESP you are using.

  1. I've run a trial blinky sketch and it appears to have close to 50K free, so it wont quite do it...
    I might need something like an FRAM of sorts for the buffer. It should read/write quite fast.

I striped down the ILI9341 library to x/y and color coordinates. Text is printed in a pixel by pixel basis. I am still to check with lines and circles, as the library uses some built in features on the display controller itself.

Serial print of my 16*77 pixels display memory using a UNO.

There are several ESP8266's available the smallest being the ESP-01 in which you would have about 50k's left of RAM, you could attempt to write your file to SPIFFS though that would complicate your program quite a lot. An ESP-07 already has quite a bit more memory and an ESP-12 as is on the nodeMCU boards even more

I striped down the ILI9341 library to x/y and color coordinates. Text is printed in a pixel by pixel basis. I am still to check with lines and circles, as the library uses some built in features on the display controller itself.

You should be using the Adafruit_GFX library, (IL9341 library does as well)
From Adafruit_GFX.h

class Adafruit_GFX : public Print {

 public:

  Adafruit_GFX(int16_t w, int16_t h); // Constructor

  // This MUST be defined by the subclass:
  virtual void drawPixel(int16_t x, int16_t y, uint16_t color) = 0;

  // These MAY be overridden by the subclass to provide device-specific
  // optimized code.  Otherwise 'generic' versions are used.
  virtual void
    drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color),
    drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color),
    drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color),
    drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color),
    fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color),
    fillScreen(uint16_t color),
    invertDisplay(boolean i);

  // These exist only with Adafruit_GFX (no subclass overrides)
  void
    drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color),
    drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername,
      uint16_t color),
    fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color),
    fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername,
      int16_t delta, uint16_t color),
    drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
      int16_t x2, int16_t y2, uint16_t color),
    fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
      int16_t x2, int16_t y2, uint16_t color),
    drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h,
      int16_t radius, uint16_t color),
    fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h,
      int16_t radius, uint16_t color),
    drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap,
      int16_t w, int16_t h, uint16_t color),
    drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap,
      int16_t w, int16_t h, uint16_t color, uint16_t bg),
    drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, 
      int16_t w, int16_t h, uint16_t color),
    drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color,
      uint16_t bg, uint8_t size),
    setCursor(int16_t x, int16_t y),
    setTextColor(uint16_t c),
    setTextColor(uint16_t c, uint16_t bg),
    setTextSize(uint8_t s),
    setTextWrap(boolean w),
    setRotation(uint8_t r);

All you need to do is
create a 'void drawPixel(int16_t x, int16_t y, uint16_t color) = 0;'
i would event just keep the 16-bit color as argument and convert to 8 bit grayscale within that (5bit-R)*2+(6bit-G)+(5bit-B)*2 should nearly to the trick, maybe check for maximum range. Or go for a 16-bit color image.
In fact you could just enter the foreground (and background color) as 8-bit grayscale and it will come out the same the other end. Adafruit_GFX merely plots it all in the right spot but does nothing to the color value itself (and that it si stored as a 16-bit value within the function is not really relevant and doesn't waste more than a few bytes as long as your drawPixel() stores an 8-bit value again.

I'm using the Adafruit GFX, but through calls - my modified ILI9341 essentially is an interface that converts standard display commands into something useful for my output and adds an abstraction layer to the user.

So I think I will always need that, there is a lot of under the hood code? Or can I be missing something?

In the code I use things such as:

tft.print("...");
tft.fillRect(...);
tft.drawLine(...);

I also set the screen resolutions, output type (grayscale, 256 colors, 1 bit). etc...

This is the core part of the code. It prints pixels and deals with compression (1 bit, grayscale, 256 colors...))
Then I have other functions that use that to print text, squares, etc.

volatile uint8_t biDimArray2 [ILI9341_TFTWIDTH/8] [ILI9341_TFTHEIGHT] = {0};    // Compressed
//volatile uint8_t biDimArray2 [ILI9341_TFTWIDTH] [ILI9341_TFTHEIGHT] = {0};      // Uncompressed

void Adafruit_ILI9341::refresh(int8_t c) {
    //Serial.println(c);
    for(int16_t y=0; y<ILI9341_TFTHEIGHT; y++) 
  {
       // This routine prints the memory contents
       for(int16_t x=0; x<(ILI9341_TFTWIDTH/8); x++)
       {        

        int8_t ljy = (biDimArray2 [x][y]); //Commented above, outside the loop        
        // Expand to cover 8 pixels    
        for(int8_t j=0; j<8; j++)   // This is only needed if using compresion
{       
       // This one prints every bit on the byte using only 1/8 of the memory for B/W        
       // for (int8_t i=7; i>=0; i--)
          {       
            if(bitRead(ljy, j))         // with compression  
            //if (ljy)                  // without compression
            {               
                Serial.print("*");
            }
            else
            {
                Serial.print(" ");
            }

          }
}
      }
        Serial.println();
   }
}

An image stored in flash memory can be read and sent with very little RAM used. Uno has 32K flash for program (a few K), labels and tables.

8-bit grayscale pixels, 16x77 needs 1232 bytes. Uno could store 20 such images and still have room for a lot of code.

Very big images can be stored on SD, an Uno can stream data from file to print way faster than most bauds print.