Improved Adafruit GFX bitmap drawing speeds on Arduino

Posted June 11th, 2016 at 10:00 AM


We used the Adafruit GFX library when interfacing with an EastRising ER-TFTM070-5 capacitive touch LCD panel. The only drawback we found was drawing bitmaps. A 320x140 monochrome logo using drawBitmap took 3-4 secs to draw and consumed 6222 bytes of PROGMEM on an Ardiuno Mega. The equivalent Run length encoded (RLE) bitmap displayed in less than a second and consumed only 2772 bytes.

We used C# code to generate the RLE bitmap from a black and white .bmp file. We are grateful to 'devundef' at StackOverflow for C# bitmap data parsing functions.

LockBits allows individual pixels to be referenced and tested. GetIndexedPixel determines if a bit is black or white. The bitmap is a background of black with white detail so a change from black to white signals the start of a white run that continues until a change to black.

int x,y;
int[] runstartx = new int[10000];
int[] runstarty = new int[10000];
int[] runlens = new int[10000];
int runs = 0;               
                
Bitmap bm = new Bitmap("BWbitmapfile.bmp");

// lock all image bits
var bitmapData = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), 
    ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);

int running = 0;

for (y = 0; y < bm.Height; y++)
{
    for (x = 0; x < bm.Width; x++)
    {
        if (GetIndexedPixel(x,y, bitmapData) == 0)
        {
            // pixel is black
            if (running > 0)
            {
                // save run
                runlens[runs++] = running;  // save run length
                running = 0;
            }
        }
        else
        {
            // pixel is one-white
            if (running == 0)
            {
                running = 1;
                // start start coordinates
                runstartx[runs] = x;
                runstarty[runs] = y;
            }
            else
            {
                running += 1;
            }
        }             
    }
}

textBox1.Text += "Width: " + bm.Width.ToString() + " Height: "  + bm.Height.ToString() + " Runs: " + runs.ToString() + "\r\n\r\n";
textBox1.Text += "Runs: (X,Y,Length)" + "\r\n";

for (x = 0; x < runs; x++)
{
    textBox1.Text += runstartx[x].ToString() + ',' + runstarty[x].ToString() + ',' + runlens[x].ToString() + ",\r\n";
}

private int GetIndexedPixel(int x, int y, BitmapData bitmapData)
{
    var index = y * bitmapData.Stride + (x >> 3);
    var chunk = Marshal.ReadByte(bitmapData.Scan0, index);
    var mask = (byte)(0x80 >> (x & 0x7));
    return (chunk & mask) == mask ? 1 : 0;
}

Run the C# code and the RLE runs appear in a textbox for cut and pasting as PRGOMEM code into an Arduino project. The RLE bitmap is a series of horizontal lines defined as runstartx, runstarty, runlength integers

static const uint16_t rlebitmap[] PROGMEM =
{
287,30,15,
15,31,32,
106,31,11,
220,99,11,
281,99,26,
286,100,16
};

The RLE bitmap is drawn by Arduino code iterating through the PROGMEM bitmap using drawFastHLine to draw individual runs:

void drawRLEBitmap(word xp, word yp)
{
    int blen = sizeof(rlebitmap) / sizeof(uint16_t);

    int i;
    word x;
    word y;
    word runlen;
    
    for (i = 0; i < blen;i += 3)
    {
      x = pgm_read_word(rlebitmap + i);
      y = pgm_read_word(rlebitmap + i + 1);
      runlen = pgm_read_word(rlebitmap + i + 2);
      tft.drawFastHLine(x + xp, y + yp, runlen, RA8875_WHITE);
    }
}