Buffered_GFX: template class for Arduino Due to reduce flicker

Topic created by Jean-Marc Zingg

Buffered_GFX and Buffered_GFX_Window are template classes that buffer monochrome text and graphics produced with Adafruit_GFX.
They to allow to write the buffer content in one go to the display.

The method "updateToTarget()" uses the fast update method of the target display library, where available: pushColor or writePixel.
Adaptation to the target library is done by "partial specialization" of the template classes. Such specializations can be added easily.

A demo and test example shows how to use it.

This source is "help yourself" software, comes without warranty and support.
Try it, to see if it's useful for you.

Template class Buffered_GFX:

// Buffered_GFX.h
//
// JZ 20.12.2016
//
// include target class header before this file, for specialization to take effect
//
// the purpose of this template class is to reduce flicker for text output.
// the call to updateToTarget() overwrites the TargetDisplay with the buffer content in one go.
//
// can be used with "Arduinos" with enough RAM, e.g. Arduino Due, ESP8266, STM32.
//
// w and h must correspond to the initial dimension of the TargetDisplay.
// by design, the buffered display and its content DO NOT rotate, if the TargetDisplay is rotated.
// applying rotation to the Buffered_GFX is effective only for content drawn to it afterwards.
//
// this is "help yourself software" : no warranty, no support,
// feel free to use, modify, improve, enhance, re-post etc.

#ifndef _Buffered_GFX_H_
#define _Buffered_GFX_H_

#ifndef _Buffered_GFX_Base_I_
#define _Buffered_GFX_Base_I_

#include <Adafruit_GFX.h>

// base class to provide common functionality and inheritance from Adafruit_GFX
// ****************************************************************************

class Buffered_GFX_Base  : public Adafruit_GFX
{
  protected:
    uint16_t _width;
    uint16_t _height;
    uint16_t _textfgcolor, _textbgcolor;
    uint32_t _elapsed;
    uint8_t* _monochrome_buffer;
    void printXY(const char* text, uint16_t x, uint16_t y, uint16_t x1, uint16_t y1)
    { // no printf in Print.h of package SAM
      Serial.print(text);
      Serial.print("(");
      Serial.print(x);
      Serial.print(",");
      Serial.print(y);
      Serial.print(",");
      Serial.print(x1);
      Serial.print(",");
      Serial.print(y1);
      Serial.println(")");
    };
  public:
    Buffered_GFX_Base(uint16_t w, uint16_t h, uint8_t buffer[]) :
      Adafruit_GFX(w, h),
      _monochrome_buffer(buffer),
      _width(w), _height(h)
    {
      _textfgcolor = 0xFFFF;
      _textbgcolor = 0x0000;
      _elapsed = 0;
      fillBuffer(0x0000);
    };
  public:
    virtual void drawPixel(int16_t x, int16_t y, uint16_t color)
    {
      if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) return;
      int16_t bx = x;
      int16_t by = y;
      switch (getRotation())
      {
        case 1:
          bx = _width - y - 1;
          by = x;
          break;
        case 2:
          bx = _width - x - 1;
          by = _height - y - 1;
          break;
        case 3:
          bx = y;
          by = _height - x - 1;
          break;
      }
      uint32_t index = (bx + by * _width) / 8;
      if (color == _textbgcolor)
      {
        _monochrome_buffer[index] &= ~(0x80 >> bx % 8);
      }
      else
      {
        _monochrome_buffer[index] |= (0x80 >> bx % 8);
      }
    };
    void setTextColor(uint16_t color)
    {
      _textfgcolor = color;
    };
    void setTextColor(uint16_t fgcolor, uint16_t bgcolor)
    {
      _textfgcolor = fgcolor;
      _textbgcolor = bgcolor;
    };
    void fillBuffer(uint16_t color)
    {
      for (uint32_t index = 0; index < _width *  _height / 8; index++)
      {
        _monochrome_buffer[index] = ((color != _textbgcolor) ? 0xFF : 0x00);
      }
    };
    uint32_t elapsed()
    {
      return _elapsed;
    };
};
#endif // _Buffered_GFX_Base_I_

// template class for full screen buffering
// ****************************************

template<typename Target_t, uint16_t w, uint16_t h>
class Buffered_GFX : public Buffered_GFX_Base
{
  private:
    Target_t& TargetDisplay;
    uint8_t _monochrome_buffer[w * h / 8];
  public:
    Buffered_GFX(Target_t& target) : 
    Buffered_GFX_Base(w, h, _monochrome_buffer), 
    TargetDisplay(target) 
    {};
    // by (my) design, the general method of the template class is for TFT_HX8357_Due and "pushColor" and "setWindow"
    void updateToTarget()
    {
      uint32_t start = micros();
      // #ifdef _TFT_HX8357_DueH_ // "quick and dirty" solution
      TargetDisplay.setWindow(0, 0, TargetDisplay.width() - 1, TargetDisplay.height() - 1); // my preferred name for the public method
      // #else
      // TargetDisplay.setAddrWindow(0, 0, TargetDisplay.width() - 1, TargetDisplay.height() - 1); // but most libraries use this name
      // #endif
      switch (TargetDisplay.getRotation())
      {
        case 0:
          { // is this faster ? yes, slighly
            uint32_t limit = w * h / 8;
            for (uint32_t index = 0; index < limit; index++)
            {
              uint8_t data = _monochrome_buffer[index];
              for (uint8_t pixel = 0; pixel < 8; pixel++)
              {
                TargetDisplay.pushColor((data & 0x80) ? _textfgcolor : _textbgcolor);
                data <<= 1;
              }
            }
          }
          break;
        case 1:
          for (int16_t x = w - 1; x >= 0; x--)
          {
            for (int16_t y = 0; y < h; y++)
            {
              TargetDisplay.pushColor(((_monochrome_buffer[(x + y * w) / 8]) & (0x80 >> x % 8)) ? _textfgcolor : _textbgcolor);
            }
          }
          break;
        case 2:
          for (int16_t y = h - 1; y >= 0; y--)
          {
            for (int16_t x = w - 1; x >= 0; x--)
            {
              TargetDisplay.pushColor(((_monochrome_buffer[(x + y * w) / 8]) & (0x80 >> x % 8)) ? _textfgcolor : _textbgcolor);
            }
          }
          break;
        case 3:
          for (int16_t x = 0; x < w; x++)
          {
            for (int16_t y = h - 1; y >= 0; y--)
            {
              TargetDisplay.pushColor(((_monochrome_buffer[(x + y * w) / 8]) & (0x80 >> x % 8)) ? _textfgcolor : _textbgcolor);
            }
          }
          break;
      }
      _elapsed = micros() - start;
    };
};
#endif // _Buffered_GFX_H_

Template class Buffered_GFX_Window:

Would exceed the allowed limit. See attached zipfile.

Template class specializations for Buffered_GFX:

Would exceed the allowed limit. See attached zipfile.

Buffered_GFX_V2.zip (10.7 KB)

Template class specializations for Buffered_GFX:

// Buffered_GFX_Specializations.h
//
// JZ 20.12.2016
//
// include target class header before this file, for specialization to take effect
//

#ifndef _Buffered_GFX_Specializations_H_
#define _Buffered_GFX_Specializations_H_

#include "Buffered_GFX.h"

#if defined(MCUFRIEND_KBV_H_) && !defined(Test_without_pushColor) 

// template class for full screen buffering : partial specialization for MCUFRIEND_kbv
// ***********************************************************************************

template<uint16_t w, uint16_t h>
class Buffered_GFX<MCUFRIEND_kbv, w, h> : public Buffered_GFX_Base
{
  private:
    enum E {cb_size = 64};
    MCUFRIEND_kbv& TargetDisplay;
    uint8_t _monochrome_buffer[w * h / 8];
    uint16_t _color_buffer[cb_size]; // using a _color_buffer is faster for MCUFRIEND_kbv
  public:
    Buffered_GFX(MCUFRIEND_kbv& target) :
      Buffered_GFX_Base(w, h, _monochrome_buffer),
      TargetDisplay(target)
    {};
    void updateToTarget()
    {
      uint32_t start = micros();
      TargetDisplay.setAddrWindow(0, 0, TargetDisplay.width() - 1, TargetDisplay.height() - 1);
      uint16_t cb_index = 0;
      bool first = true;
      uint32_t limit = w * h / 8;
      switch (TargetDisplay.getRotation())
      {
        case 0:
          // is this faster ?
          for (uint32_t index = 0; index < limit; index++)
          {
            uint8_t data = _monochrome_buffer[index];
            for (uint8_t pixel = 0; pixel < 8; pixel++)
            {
              _color_buffer[cb_index] = ((data & 0x80) ? _textfgcolor : _textbgcolor);
              data <<= 1;
              cb_index++;
              if (cb_index >= cb_size)
              {
                TargetDisplay.pushColors(_color_buffer, cb_size, first);
                cb_index = 0;
                first = false;
              }
            }
          }
          break;
        case 1:
          for (int16_t x = w - 1; x >= 0; x--)
          {
            for (int16_t y = 0; y < h; y++)
            {
              _color_buffer[cb_index] = ((_monochrome_buffer[(x + y * w) / 8]) & (0x80 >> x % 8)) ? _textfgcolor : _textbgcolor;
              cb_index++;
              if (cb_index >= cb_size)
              {
                TargetDisplay.pushColors(_color_buffer, cb_size, first);
                cb_index = 0;
                first = false;
              }
            }
          }
          break;
        case 2:
          for (int16_t y = h - 1; y >= 0; y--)
          {
            for (int16_t x = w - 1; x >= 0; x--)
            {
              _color_buffer[cb_index] = ((_monochrome_buffer[(x + y * w) / 8]) & (0x80 >> x % 8)) ? _textfgcolor : _textbgcolor;
              cb_index++;
              if (cb_index >= cb_size)
              {
                TargetDisplay.pushColors(_color_buffer, cb_size, first);
                cb_index = 0;
                first = false;
              }
            }
          }
          break;
        case 3:
          for (int16_t x = 0; x < w; x++)
          {
            for (int16_t y = h - 1; y >= 0; y--)
            {
              _color_buffer[cb_index] = ((_monochrome_buffer[(x + y * w) / 8]) & (0x80 >> x % 8)) ? _textfgcolor : _textbgcolor;
              cb_index++;
              if (cb_index >= cb_size)
              {
                TargetDisplay.pushColors(_color_buffer, cb_size, first);
                cb_index = 0;
                first = false;
              }
            }
          }
          break;
      }
      if (cb_index > 0)
      {
        TargetDisplay.pushColors(_color_buffer, cb_index, first);
      }
      _elapsed = micros() - start;
    };
};
#endif // MCUFRIEND_KBV_H_

#if defined(_ILI9341_dueH_)  && !defined(Test_without_pushColor) 

// template class for full screen buffering : partial specialization for ILI9341_due
// *********************************************************************************

template<uint16_t w, uint16_t h>
class Buffered_GFX<ILI9341_due, w, h > : public Buffered_GFX_Base
{
  private:
    ILI9341_due& TargetDisplay;
    uint8_t _monochrome_buffer[w * h / 8];
    // using a color_buffer would be only <10% faster for  ILI9341_due
  public:
    Buffered_GFX(ILI9341_due& target) : 
    Buffered_GFX_Base(w, h, _monochrome_buffer),
    TargetDisplay(target) 
    {};
    void updateToTarget()
    {
      uint32_t start = micros();
      TargetDisplay.setAddrWindow(0, 0, TargetDisplay.width() - 1, TargetDisplay.height() - 1);
      switch (TargetDisplay.getRotation())
      {
        case 0:
          { // is this faster ? slighly
            uint32_t limit = w * h / 8;
            for (uint32_t index = 0; index < limit; index++)
            {
              uint8_t data = _monochrome_buffer[index];
              for (uint8_t pixel = 0; pixel < 8; pixel++)
              {
                TargetDisplay.pushColor((data & 0x80) ? _textfgcolor : _textbgcolor);
                data <<= 1;
              }
            }
          }
          break;
        case 1:
          for (int16_t x = w - 1; x >= 0; x--)
          {
            for (int16_t y = 0; y < h; y++)
            {
              TargetDisplay.pushColor(((_monochrome_buffer[(x + y * w) / 8]) & (0x80 >> x % 8)) ? _textfgcolor : _textbgcolor);
            }
          }
          break;
        case 2:
          for (int16_t y = h - 1; y >= 0; y--)
          {
            for (int16_t x = w - 1; x >= 0; x--)
            {
              TargetDisplay.pushColor(((_monochrome_buffer[(x + y * w) / 8]) & (0x80 >> x % 8)) ? _textfgcolor : _textbgcolor);
            }
          }
          break;
        case 3:
          for (int16_t x = 0; x < w; x++)
          {
            for (int16_t y = h - 1; y >= 0; y--)
            {
              TargetDisplay.pushColor(((_monochrome_buffer[(x + y * w) / 8]) & (0x80 >> x % 8)) ? _textfgcolor : _textbgcolor);
            }
          }
          break;
      }
      _elapsed = micros() - start;
    };
};
#endif // _ILI9341_dueH_

#if defined(UTFT_h) && !defined(Test_without_pushColor) 

// template class for full screen buffering : partial specialization for UTFT
// **************************************************************************

template<uint16_t w, uint16_t h>
class Buffered_GFX<UTFT, w, h > : public Buffered_GFX_Base
{
  private:
    UTFT& TargetDisplay;
    uint8_t _monochrome_buffer[w * h / 8];
  public:
    Buffered_GFX(UTFT& target) : 
      Buffered_GFX_Base(w, h, _monochrome_buffer),
      TargetDisplay(target)
    {};
    void updateToTarget()
    {
      uint32_t start = micros();
      cbi(TargetDisplay.P_CS, TargetDisplay.B_CS);
      if (TargetDisplay.orient == PORTRAIT)
      {
        TargetDisplay.setXY(0, 0, h - 1, w - 1);
      }
      else
      {
        TargetDisplay.setXY(0, 0, w - 1, h - 1);
      }
      for (int16_t x = w - 1; x >= 0; x--)
      {
        for (int16_t y = 0; y < h; y++)
        {
          TargetDisplay.setPixel((_monochrome_buffer[(x + y * w) / 8] & (0x80 >> x % 8)) ? _textfgcolor : _textbgcolor);
        }
      }
      sbi(TargetDisplay.P_CS, TargetDisplay.B_CS);
      TargetDisplay.clrXY();
      _elapsed = micros() - start;
    };
};
#endif // UTFT_h