Files
TFT_eSPI/Extensions/Sprite.cpp
T
Bodmer 0e0fd75277 Raise to version 2.0.0
The library has been cleaned up as it has got a bit untidy due to the large number of small incremental changes.

4bit Sprite examples renaed to be consistent with others.

alphaBlend example moved to generic folder (alphaBlend fn was moved to TFT_eSPI class).
Added sections + explanatory comments to functions prototypes.

Temporary comments added for potential gotchas for noobs when using DMA.

spi_begin/end functions renamed to reflect functionality. Old fns  retained for backwards compatibility with user setup.h files.
2020-01-26 21:17:49 +00:00

2516 lines
74 KiB
C++

/**************************************************************************************
// The following class creates Sprites in RAM, graphics can then be drawn in the Sprite
// and rendered quickly onto the TFT screen. The class inherits the graphics functions
// from the TFT_eSPI class. Some functions are overridden by this class so that the
// graphics are written to the Sprite rather than the TFT.
// Coded by Bodmer, see license file in root folder
***************************************************************************************/
/***************************************************************************************
// Color bytes are swapped when writing to RAM, this introduces a small overhead but
// there is a nett performance gain by using swapped bytes.
***************************************************************************************/
/***************************************************************************************
** Function name: TFT_eSprite
** Description: Class constructor
*************************************************************************************x*/
TFT_eSprite::TFT_eSprite(TFT_eSPI *tft)
{
_tft = tft; // Pointer to tft class so we can call member functions
_iwidth = 0; // Initialise width and height to 0 (it does not exist yet)
_iheight = 0;
_bpp = 16;
_iswapBytes = false; // Do not swap pushImage colour bytes by default
_created = false;
_xs = 0; // window bounds for pushColor
_ys = 0;
_xe = 0;
_ye = 0;
_xptr = 0; // pushColor coordinate
_yptr = 0;
_xpivot = 0;
_ypivot = 0;
_colorMap = nullptr;
this->cursor_y = this->cursor_x = 0; // Text cursor position
}
/***************************************************************************************
** Function name: createSprite
** Description: Create a sprite (bitmap) of defined width and height
*************************************************************************************x*/
// cast returned value to (uint8_t*) for 8 bit or (uint16_t*) for 16 bit colours
void* TFT_eSprite::createSprite(int16_t w, int16_t h, uint8_t frames)
{
if ( _created ) return _img8_1;
if ( w < 1 || h < 1 ) return NULL;
_iwidth = _dwidth = _bitwidth = w;
_iheight = _dheight = h;
_colorMap = nullptr;
this->cursor_x = 0;
this->cursor_y = 0;
// Default scroll rectangle and gap fill colour
_sx = 0;
_sy = 0;
_sw = w;
_sh = h;
_scolor = TFT_BLACK;
_xpivot = w/2;
_ypivot = h/2;
_img8 = (uint8_t*) callocSprite(w, h, frames);
_img8_1 = _img8;
_img8_2 = _img8;
_img = (uint16_t*) _img8;
_img4 = _img8;
// This is to make it clear what pointer size is expected to be used
// but casting in the user sketch is needed due to the use of void*
if (_bpp == 1)
{
w = (w+7) & 0xFFF8;
_img8_2 = _img8 + ( (w>>3) * h + 1 );
}
if (_img8)
{
_created = true;
return _img8;
}
return NULL;
}
/***************************************************************************************
** Function name: ~TFT_eSprite
** Description: Class destructor
*************************************************************************************x*/
TFT_eSprite::~TFT_eSprite(void)
{
deleteSprite();
}
/***************************************************************************************
** Function name: callocSprite
** Description: Allocate a memory area for the Sprite and return pointer
*************************************************************************************x*/
void* TFT_eSprite::callocSprite(int16_t w, int16_t h, uint8_t frames)
{
// Add one extra "off screen" pixel to point out-of-bounds setWindow() coordinates
// this means push/writeColor functions do not need additional bounds checks and
// hence will run faster in normal circumstances.
uint8_t* ptr8 = NULL;
if (_bpp == 16)
{
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
if ( psramFound() ) ptr8 = ( uint8_t*) ps_calloc(w * h + 1, sizeof(uint16_t));
else
#endif
ptr8 = ( uint8_t*) calloc(w * h + 1, sizeof(uint16_t));
}
else if (_bpp == 8)
{
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
if ( psramFound() ) ptr8 = ( uint8_t*) ps_calloc(w * h + 1, sizeof(uint8_t));
else
#endif
ptr8 = ( uint8_t*) calloc(w * h + 1, sizeof(uint8_t));
}
else if (_bpp == 4)
{
w = (w+1) & 0xFFFE; // width needs to be multiple of 2, with an extra "off screen" pixel
_iwidth = w;
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
if ( psramFound() ) ptr8 = ( uint8_t*) ps_calloc(((w * h) >> 1) + 1, sizeof(uint8_t));
else
#endif
ptr8 = ( uint8_t*) calloc(((w * h) >> 1) + 1, sizeof(uint8_t));
}
else // Must be 1 bpp
{
//_dwidth Display width+height in pixels always in rotation 0 orientation
//_dheight Not swapped for sprite rotations
// Note: for 1bpp _iwidth and _iheight are swapped during Sprite rotations
w = (w+7) & 0xFFF8; // width should be the multiple of 8 bits to be compatible with epdpaint
_iwidth = w; // _iwidth is rounded up to be multiple of 8, so might not be = _dwidth
_bitwidth = w;
if (frames > 2) frames = 2; // Currently restricted to 2 frame buffers
if (frames < 1) frames = 1;
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
if ( psramFound() ) ptr8 = ( uint8_t*) ps_calloc(frames * (w>>3) * h + frames, sizeof(uint8_t));
else
#endif
ptr8 = ( uint8_t*) calloc(frames * (w>>3) * h + frames, sizeof(uint8_t));
}
return ptr8;
}
/***************************************************************************************
** Function name: createPalette (from RAM array)
** Description: Set a palette for a 4-bit per pixel sprite
*************************************************************************************x*/
void TFT_eSprite::createPalette(uint16_t colorMap[], int colors)
{
if (_colorMap != nullptr)
{
free(_colorMap);
}
if (colorMap == nullptr)
{
return; // do nothing other than clear the existing map
}
// allocate color map
_colorMap = (uint16_t *)calloc(16, sizeof(uint16_t));
if (colors > 16)
colors = 16;
for (auto i = 0; i < colors; i++)
{
_colorMap[i] = colorMap[i];
}
}
/***************************************************************************************
** Function name: createPalette (from FLASH array)
** Description: Set a palette for a 4-bit per pixel sprite
*************************************************************************************x*/
void TFT_eSprite::createPalette(const uint16_t colorMap[], int colors)
{
if (_colorMap != nullptr)
{
free(_colorMap);
}
if (colorMap == nullptr)
{
return; // do nothing other than clear the existing map
}
// allocate color map
_colorMap = (uint16_t *)calloc(16, sizeof(uint16_t));
if (colors > 16)
colors = 16;
for (auto i = 0; i < colors; i++)
{
_colorMap[i] = pgm_read_word(colorMap++);
}
}
/***************************************************************************************
** Function name: frameBuffer
** Description: For 1 bpp Sprites, select the frame used for graphics
*************************************************************************************x*/
// Frames are numbered 1 and 2
void* TFT_eSprite::frameBuffer(int8_t f)
{
if (!_created) return NULL;
if (_bpp == 16) return _img;
if (_bpp == 8) return _img8;
if (_bpp == 4) return _img4;
if ( f == 2 ) _img8 = _img8_2;
else _img8 = _img8_1;
return _img8;
}
/***************************************************************************************
** Function name: setColorDepth
** Description: Set bits per pixel for colour (1, 8 or 16)
*************************************************************************************x*/
void* TFT_eSprite::setColorDepth(int8_t b)
{
// Can't change an existing sprite's colour depth so delete it
if (_created) free(_img8_1);
// Now define the new colour depth
if ( b > 8 ) _bpp = 16; // Bytes per pixel
else if ( b > 4 ) _bpp = 8;
else if ( b > 1 ) _bpp = 4;
else _bpp = 1;
// If it existed, re-create the sprite with the new colour depth
if (_created)
{
_created = false;
return createSprite(_iwidth, _iheight);
}
return NULL;
}
/***************************************************************************************
** Function name: getColorDepth
** Description: Get bits per pixel for colour (1, 8 or 16)
*************************************************************************************x*/
int8_t TFT_eSprite::getColorDepth(void)
{
if (_created) return _bpp;
else return 0;
}
/***************************************************************************************
** Function name: setBitmapColor
** Description: Set the foreground foreground and background colour
***************************************************************************************/
void TFT_eSprite::setBitmapColor(uint16_t c, uint16_t b)
{
if (c == b) b = ~c;
_tft->bitmap_fg = c;
_tft->bitmap_bg = b;
}
/***************************************************************************************
** Function name: setPaletteColor
** Description: Set the palette color at the given index
***************************************************************************************/
void TFT_eSprite::setPaletteColor(uint8_t index, uint16_t color)
{
if (_colorMap == nullptr || index > 15)
return; // out of bounds
_colorMap[index] = color;
}
/***************************************************************************************
** Function name: getPaletteColor
** Description: Return the palette color at index, or 0 (black) on error.
***************************************************************************************/
uint16_t TFT_eSprite::getPaletteColor(uint8_t index)
{
if (_colorMap == nullptr || index > 15)
return 0;
return _colorMap[index];
}
/***************************************************************************************
** Function name: deleteSprite
** Description: Delete the sprite to free up memory (RAM)
*************************************************************************************x*/
void TFT_eSprite::deleteSprite(void)
{
if (!_created ) return;
if (_colorMap != nullptr)
{
free(_colorMap);
}
free(_img8_1);
_created = false;
}
/***************************************************************************************
** Function name: setPivot
** Description: Set the pivot point in this Sprite
*************************************************************************************x*/
void TFT_eSprite::setPivot(int16_t x, int16_t y)
{
_xpivot = x;
_ypivot = y;
}
/***************************************************************************************
** Function name: getPivotX
** Description: Get the x pivot position
***************************************************************************************/
int16_t TFT_eSprite::getPivotX(void)
{
return _xpivot;
}
/***************************************************************************************
** Function name: getPivotY
** Description: Get the y pivot position
***************************************************************************************/
int16_t TFT_eSprite::getPivotY(void)
{
return _ypivot;
}
/***************************************************************************************
** Function name: pushRotated - Fast fixed point integer maths version
** Description: Push rotated Sprite to TFT screen
*************************************************************************************x*/
#define FP_SCALE 10
bool TFT_eSprite::pushRotated(int16_t angle, int32_t transp)
{
if ( !_created ) return false;
// Trig values for the rotation
float radAngle = -angle * 0.0174532925; // Convert degrees to radians
float sinraf = sin(radAngle);
float cosraf = cos(radAngle);
int32_t sinra = sinraf * (1<<FP_SCALE);
int32_t cosra = cosraf * (1<<FP_SCALE);
// Bounding box parameters
int16_t min_x;
int16_t min_y;
int16_t max_x;
int16_t max_y;
// Get the bounding box of this rotated source Sprite relative to Sprite pivot
getRotatedBounds(sinraf, cosraf, width(), height(), _xpivot, _ypivot, &min_x, &min_y, &max_x, &max_y);
// Move bounding box so source Sprite pivot coincides with TFT pivot
min_x += _tft->_xpivot;
max_x += _tft->_xpivot;
min_y += _tft->_ypivot;
max_y += _tft->_ypivot;
// Test only to show bounding box on TFT
// _tft->drawRect(min_x, min_y, max_x - min_x + 1, max_y - min_y + 1, TFT_GREEN);
// Return if bounding box is outside of TFT area
if (min_x > _tft->width()) return true;
if (min_y > _tft->height()) return true;
if (max_x < 0) return true;
if (max_y < 0) return true;
// Clip bounding box to be within TFT area
if (min_x < 0) min_x = 0;
if (min_y < 0) min_y = 0;
if (max_x > _tft->width()) max_x = _tft->width();
if (max_y > _tft->height()) max_y = _tft->height();
uint16_t sline_buffer[max_y - min_y + 1];
int32_t xt = min_x - _tft->_xpivot;
int32_t yt = min_y - _tft->_ypivot;
// Keep multiply out of the loop
int32_t cxt = cosra * xt + (_xpivot<<FP_SCALE);
int32_t sxt = sinra * xt + (_ypivot<<FP_SCALE);
int32_t init_cyt = cosra * yt;
int32_t init_syt = sinra * yt;
_tft->startWrite(); // ESP32: avoid transaction overhead for every tft pixel
// Scan destination bounding box and fetch transformed pixels from source Sprite
for (int32_t x = min_x; x <= max_x; x++) {
cxt += cosra;
sxt += sinra;
bool column_drawn = false;
uint32_t pixel_count = 0;
int32_t y_start = 0;
int32_t xs = cxt - init_syt;
int32_t cyt = init_cyt;
for (int32_t y = min_y; y <= max_y; y++) {
cyt += cosra;
xs -= sinra;
// Do not calculate yp unless xp is in bounds
int32_t xp = truncateFP(xs, FP_SCALE);
if ((xp >= 0) && (xp < _iwidth))
{
int32_t yp = truncateFP(sxt + cyt, FP_SCALE);
// Check if yp is in bounds
if ((yp >= 0) && (yp < _iheight)) {
uint32_t rp;
if (_bpp == 16) {rp = _img[xp + yp * _iwidth]; rp = rp>>8 | rp<<8; }
else rp = readPixel(xp, yp);
if (transp >= 0 ) {
if (rp != transp) _tft->drawPixel(x, y, rp);
}
else {
if (!column_drawn) y_start = y;
sline_buffer[pixel_count++] = rp>>8 | rp<<8;
}
column_drawn = true;
}
}
else if (column_drawn) y = max_y; // Skip remaining column pixels
}
if (pixel_count) _tft->pushImage(x, y_start, 1, pixel_count, sline_buffer);
}
_tft->endWrite(); // ESP32: end transaction
return true;
}
/***************************************************************************************
** Function name: pushRotatedHP - Higher Precision version of pushRotated
** Description: Push rotated Sprite to TFT screen
*************************************************************************************x*/
bool TFT_eSprite::pushRotatedHP(int16_t angle, int32_t transp)
{
if ( !_created ) return false;
// Trig values for the rotation
float radAngle = -angle * 0.0174532925; // Convert degrees to radians
float sinra = sin(radAngle);
float cosra = cos(radAngle);
// Bounding box parameters
int16_t min_x;
int16_t min_y;
int16_t max_x;
int16_t max_y;
// Get the bounding box of this rotated source Sprite relative to Sprite pivot
getRotatedBounds(sinra, cosra, width(), height(), _xpivot, _ypivot, &min_x, &min_y, &max_x, &max_y);
// Move bounding box so source Sprite pivot coincides with TFT pivot
min_x += _tft->_xpivot;
max_x += _tft->_xpivot;
min_y += _tft->_ypivot;
max_y += _tft->_ypivot;
// Test only to show bounding box on TFT
// _tft->drawRect(min_x, min_y, max_x - min_x + 1, max_y - min_y + 1, TFT_GREEN);
// Return if bounding box is outside of TFT area
if (min_x > _tft->width()) return true;
if (min_y > _tft->height()) return true;
if (max_x < 0) return true;
if (max_y < 0) return true;
// Clip bounding box to be within TFT area
if (min_x < 0) min_x = 0;
if (min_y < 0) min_y = 0;
if (max_x > _tft->width()) max_x = _tft->width();
if (max_y > _tft->height()) max_y = _tft->height();
uint16_t sline_buffer[max_y - min_y + 1];
_tft->startWrite(); // ESP32: avoid transaction overhead for every tft pixel
// Scan destination bounding box and fetch transformed pixels from source Sprite
for (int32_t x = min_x; x <= max_x; x++) {
int32_t xt = x - _tft->_xpivot;
float cxt = cosra * xt + _xpivot;
float sxt = sinra * xt + _ypivot;
bool column_drawn = false;
uint32_t pixel_count = 0;
int32_t y_start = 0;
for (int32_t y = min_y; y <= max_y; y++) {
int32_t yt = y - _tft->_ypivot;
int32_t xs = (int32_t)round(cxt - sinra * yt);
// Do not calculate ys unless xs is in bounds
if (xs >= 0 && xs < width())
{
int32_t ys = (int32_t)round(sxt + cosra * yt);
// Check if ys is in bounds
if (ys >= 0 && ys < height()) {
int32_t rp = readPixel(xs, ys);
if (transp >= 0 ) {
if (rp != transp) _tft->drawPixel(x, y, rp);
}
else {
if (!column_drawn) y_start = y;
sline_buffer[pixel_count++] = rp>>8 | rp<<8;
}
column_drawn = true;
}
}
else if (column_drawn) y = max_y; // Skip remaining column pixels
}
if (pixel_count) _tft->pushImage(x, y_start, 1, pixel_count, sline_buffer);
}
_tft->endWrite(); // ESP32: end transaction
return true;
}
/***************************************************************************************
** Function name: pushRotated - Fast fixed point integer maths version
** Description: Push a rotated copy of the Sprite to another Sprite
*************************************************************************************x*/
bool TFT_eSprite::pushRotated(TFT_eSprite *spr, int16_t angle, int32_t transp)
{
if ( !_created ) return false; // Check this Sprite is created
if ( !spr->_created ) return false; // Ckeck destination Sprite is created
// Trig values for the rotation
float radAngle = -angle * 0.0174532925; // Convert degrees to radians
float sinraf = sin(radAngle);
float cosraf = cos(radAngle);
int32_t sinra = sinraf * (1<<FP_SCALE);
int32_t cosra = cosraf * (1<<FP_SCALE);
// Bounding box parameters
int16_t min_x;
int16_t min_y;
int16_t max_x;
int16_t max_y;
// Get the bounding box of this rotated source Sprite
getRotatedBounds(sinraf, cosraf, width(), height(), _xpivot, _ypivot, &min_x, &min_y, &max_x, &max_y);
// Move bounding box so source Sprite pivot coincides with destination Sprite pivot
min_x += spr->_xpivot;
max_x += spr->_xpivot;
min_y += spr->_ypivot;
max_y += spr->_ypivot;
// Test only to show bounding box
// spr->fillSprite(TFT_BLACK);
// spr->drawRect(min_x, min_y, max_x - min_x + 1, max_y - min_y + 1, TFT_GREEN);
// Return if bounding box is completely outside of destination Sprite
if (min_x > spr->width()) return true;
if (min_y > spr->height()) return true;
if (max_x < 0) return true;
if (max_y < 0) return true;
// Clip bounding box if it is partially within destination Sprite
if (min_x < 0) min_x = 0;
if (min_y < 0) min_y = 0;
if (max_x > spr->width()) max_x = spr->width();
if (max_y > spr->height()) max_y = spr->height();
// Scan destination bounding box and fetch transformed pixels from source Sprite
for (int32_t x = min_x; x <= max_x; x++)
{
int32_t xt = x - spr->_xpivot;
float cxt = cosra * xt + (_xpivot<<FP_SCALE);
float sxt = sinra * xt + (_ypivot<<FP_SCALE);
bool column_drawn = false;
for (int32_t y = min_y; y <= max_y; y++)
{
int32_t yt = y - spr->_ypivot;
int32_t xs = cxt - sinra * yt;
// Do not calculate yp unless xp is in bounds
int32_t xp = truncateFP(xs, FP_SCALE);
if (xp >= 0 && xp < _iwidth)
{
int32_t ys = sxt + cosra * yt;
// Check if ys is in bounds
int32_t yp = truncateFP(ys, FP_SCALE);
if (yp >= 0 && yp < _iheight)
{
uint32_t rp;
if (_bpp == 16) {rp = _img[xp + yp * _iwidth]; rp = rp>>8 | rp<<8; }
else rp = readPixel(xp, yp);
if (rp != transp) spr->drawPixel(x, y, rp);
column_drawn = true;
}
}
else if (column_drawn) y = max_y; // Skip the remaining pixels below the Sprite
}
}
return true;
}
/***************************************************************************************
** Function name: pushRotated - Higher Precision version of pushRotated
** Description: Push a rotated copy of the Sprite to another Sprite
*************************************************************************************x*/
bool TFT_eSprite::pushRotatedHP(TFT_eSprite *spr, int16_t angle, int32_t transp)
{
if ( !_created ) return false; // Check this Sprite is created
if ( !spr->_created ) return false; // Ckeck destination Sprite is created
// Trig values for the rotation
float radAngle = -angle * 0.0174532925; // Convert degrees to radians
float sinra = sin(radAngle);
float cosra = cos(radAngle);
// Bounding box parameters
int16_t min_x;
int16_t min_y;
int16_t max_x;
int16_t max_y;
// Get the bounding box of this rotated source Sprite
getRotatedBounds(sinra, cosra, width(), height(), _xpivot, _ypivot, &min_x, &min_y, &max_x, &max_y);
// Move bounding box so source Sprite pivot coincides with destination Sprite pivot
min_x += spr->_xpivot;
max_x += spr->_xpivot;
min_y += spr->_ypivot;
max_y += spr->_ypivot;
// Test only to show bounding box
// spr->fillSprite(TFT_BLACK);
// spr->drawRect(min_x, min_y, max_x - min_x + 1, max_y - min_y + 1, TFT_GREEN);
// Return if bounding box is completely outside of destination Sprite
if (min_x > spr->width()) return true;
if (min_y > spr->height()) return true;
if (max_x < 0) return true;
if (max_y < 0) return true;
// Clip bounding box if it is partially within destination Sprite
if (min_x < 0) min_x = 0;
if (min_y < 0) min_y = 0;
if (max_x > spr->width()) max_x = spr->width();
if (max_y > spr->height()) max_y = spr->height();
// Scan destination bounding box and fetch transformed pixels from source Sprite
for (int32_t x = min_x; x <= max_x; x++)
{
int32_t xt = x - spr->_xpivot;
float cxt = cosra * xt + _xpivot;
float sxt = sinra * xt + _ypivot;
bool column_drawn = false;
for (int32_t y = min_y; y <= max_y; y++)
{
int32_t yt = y - spr->_ypivot;
int32_t xs = (int32_t)round(cxt - sinra * yt);
// Do not calculate ys unless xs is in bounds
if (xs >= 0 && xs < width())
{
int32_t ys = (int32_t)round(sxt + cosra * yt);
// Check if ys is in bounds
if (ys >= 0 && ys < height())
{
uint32_t rp;
// Can avoid bounds check overhead for reading 16bpp
if (_bpp == 16) {rp = _img[xs + ys * _iwidth]; rp = rp>>8 | rp<<8; }
else rp = readPixel(xs, ys);
if (rp != transp) spr->drawPixel(x, y, rp);
column_drawn = true;
}
}
else if (column_drawn) y = max_y; // Skip the remaining pixels below the Sprite
}
}
return true;
}
/***************************************************************************************
** Function name: rotatedBounds
** Description: Get bounding box of a rotated Sprite wrt pivot
*************************************************************************************x*/
void TFT_eSprite::getRotatedBounds(float sina, float cosa, int16_t w, int16_t h, int16_t xp, int16_t yp,
int16_t *min_x, int16_t *min_y, int16_t *max_x, int16_t *max_y)
{
w -= xp; // w is now right edge coordinate relative to xp
h -= yp; // h is now bottom edge coordinate relative to yp
// Calculate new corner coordinates
int16_t x0 = -xp * cosa - yp * sina;
int16_t y0 = xp * sina - yp * cosa;
int16_t x1 = w * cosa - yp * sina;
int16_t y1 = -w * sina - yp * cosa;
int16_t x2 = h * sina + w * cosa;
int16_t y2 = h * cosa - w * sina;
int16_t x3 = h * sina - xp * cosa;
int16_t y3 = h * cosa + xp * sina;
// Find bounding box extremes, enlarge box to accomodate rounding errors
*min_x = x0-2;
if (x1 < *min_x) *min_x = x1-2;
if (x2 < *min_x) *min_x = x2-2;
if (x3 < *min_x) *min_x = x3-2;
*max_x = x0+2;
if (x1 > *max_x) *max_x = x1+2;
if (x2 > *max_x) *max_x = x2+2;
if (x3 > *max_x) *max_x = x3+2;
*min_y = y0-2;
if (y1 < *min_y) *min_y = y1-2;
if (y2 < *min_y) *min_y = y2-2;
if (y3 < *min_y) *min_y = y3-2;
*max_y = y0+2;
if (y1 > *max_y) *max_y = y1+2;
if (y2 > *max_y) *max_y = y2+2;
if (y3 > *max_y) *max_y = y3+2;
}
/***************************************************************************************
** Function name: pushSprite
** Description: Push the sprite to the TFT at x, y
*************************************************************************************x*/
void TFT_eSprite::pushSprite(int32_t x, int32_t y)
{
if (!_created) return;
if (_bpp == 16)
{
bool oldSwapBytes = _tft->getSwapBytes();
_tft->setSwapBytes(false);
_tft->pushImage(x, y, _iwidth, _iheight, _img );
_tft->setSwapBytes(oldSwapBytes);
}
else if (_bpp == 4)
{
if (_colorMap == nullptr) {
return;
}
_tft->pushImage(x, y, _dwidth, _dheight, _img4, false, _colorMap);
}
else _tft->pushImage(x, y, _dwidth, _dheight, _img8, (bool)(_bpp == 8));
}
/***************************************************************************************
** Function name: pushSprite
** Description: Push the sprite to the TFT at x, y with transparent colour
*************************************************************************************x*/
void TFT_eSprite::pushSprite(int32_t x, int32_t y, uint16_t transp)
{
if (!_created) return;
if (_bpp == 16)
{
bool oldSwapBytes = _tft->getSwapBytes();
_tft->setSwapBytes(false);
_tft->pushImage(x, y, _iwidth, _iheight, _img, transp );
_tft->setSwapBytes(oldSwapBytes);
}
else if (_bpp == 8)
{
transp = (uint8_t)((transp & 0xE000)>>8 | (transp & 0x0700)>>6 | (transp & 0x0018)>>3);
_tft->pushImage(x, y, _dwidth, _dheight, _img8, (uint8_t)transp, (bool)true);
}
else if (_bpp == 4)
{
_tft->pushImage(x, y, _dwidth, _dheight, _img4, (uint8_t)(transp & 0x0F), false, _colorMap);
}
else _tft->pushImage(x, y, _dwidth, _dheight, _img8, 0, (bool)false);
}
/***************************************************************************************
** Function name: readPixelValue
** Description: Read the color map index of a pixel at defined coordinates
*************************************************************************************x*/
uint8_t TFT_eSprite::readPixelValue(int32_t x, int32_t y)
{
if ((x < 0) || (x >= _iwidth) || (y < 0) || (y >= _iheight) || !_created) return 0xFFFF;
if (_bpp == 4)
{
uint16_t color;
if ((x & 0x01) == 0)
return ((_img4[((x+y*_iwidth)>>1)] & 0xF0) >> 4) & 0x0F; // even index = bits 7 .. 4
else
return _img4[((x-1+y*_iwidth)>>1)] & 0x0F; // odd index = bits 3 .. 0.
}
return 0;
}
/***************************************************************************************
** Function name: readPixel
** Description: Read 565 colour of a pixel at defined coordinates
*************************************************************************************x*/
uint16_t TFT_eSprite::readPixel(int32_t x, int32_t y)
{
if ((x < 0) || (x >= _iwidth) || (y < 0) || (y >= _iheight) || !_created) return 0xFFFF;
if (_bpp == 16)
{
uint16_t color = _img[x + y * _iwidth];
return (color >> 8) | (color << 8);
}
if (_bpp == 8)
{
uint16_t color = _img8[x + y * _iwidth];
if (color != 0)
{
uint8_t blue[] = {0, 11, 21, 31};
color = (color & 0xE0)<<8 | (color & 0xC0)<<5
| (color & 0x1C)<<6 | (color & 0x1C)<<3
| blue[color & 0x03];
}
return color;
}
if (_bpp == 4)
{
uint16_t color;
if ((x & 0x01) == 0)
color = _colorMap[((_img4[((x+y*_iwidth)>>1)] & 0xF0) >> 4) & 0x0F ]; // even index = bits 7 .. 4
else
color = _colorMap[_img4[((x-1+y*_iwidth)>>1)] & 0x0F]; // odd index = bits 3 .. 0.
return color;
}
if (_rotation == 1)
{
uint16_t tx = x;
x = _dwidth - y - 1;
y = tx;
}
else if (_rotation == 2)
{
x = _dwidth - x - 1;
y = _dheight - y - 1;
}
else if (_rotation == 3)
{
uint16_t tx = x;
x = y;
y = _dheight - tx - 1;
}
uint16_t color = (_img8[(x + y * _bitwidth)>>3] << (x & 0x7)) & 0x80;
if (color >> 7) return _tft->bitmap_fg;
else return _tft->bitmap_bg;
}
/***************************************************************************************
** Function name: pushImage
** Description: push 565 colour image into a defined area of a sprite
*************************************************************************************x*/
void TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data)
{
if ((x >= _iwidth) || (y >= _iheight) || (w == 0) || (h == 0) || !_created) return;
if ((x + w < 0) || (y + h < 0)) return;
int32_t xo = 0;
int32_t yo = 0;
int32_t xs = x;
int32_t ys = y;
int32_t ws = w;
int32_t hs = h;
if (x < 0) { xo = -x; ws += x; xs = 0; }
if (y < 0) { yo = -y; hs += y; ys = 0; }
if (xs + ws >= (int32_t)_iwidth) ws = _iwidth - xs;
if (ys + hs >= (int32_t)_iheight) hs = _iheight - ys;
if (_bpp == 16) // Plot a 16 bpp image into a 16 bpp Sprite
{
for (int32_t yp = yo; yp < yo + hs; yp++)
{
x = xs;
for (int32_t xp = xo; xp < xo + ws; xp++)
{
uint16_t color = data[xp + yp * w];
if(!_iswapBytes) color = color<<8 | color>>8;
_img[x + ys * _iwidth] = color;
x++;
}
ys++;
}
}
else if (_bpp == 8) // Plot a 16 bpp image into a 8 bpp Sprite
{
for (int32_t yp = yo; yp < yo + hs; yp++)
{
x = xs;
for (int32_t xp = xo; xp < xo + ws; xp++)
{
uint16_t color = data[xp + yp * w];
if(_iswapBytes) color = color<<8 | color>>8;
_img8[x + ys * _iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3);
x++;
}
ys++;
}
}
else if (_bpp == 4)
{
// not supported. The image is unlikely to have the correct colors for the color map.
// we could implement a way to push a 4-bit image using the color map?
#ifdef TFT_eSPI_DEBUG
Serial.println("pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data) not implemented");
#endif
return;
}
else // 1bpp
{
// Move coordinate rotation to support fn
if (_rotation == 1)
{
int32_t tx = x;
x = _dwidth - y - 1;
y = tx;
}
else if (_rotation == 2)
{
x = _dwidth - x - 1;
y = _dheight - y - 1;
}
else if (_rotation == 3)
{
int32_t tx = x;
x = y;
y = _dheight - tx - 1;
}
// Plot a 1bpp image into a 1bpp Sprite
uint8_t* pdata = (uint8_t* ) data;
uint32_t ww = (w+7) & 0xFFF8;
for (int32_t yp = 0; yp<h; yp++)
{
uint32_t yw = (yp * ww)>>3;
uint32_t yyp = y + yp;
for (int32_t xp = 0; xp<w; xp++)
{
uint16_t readPixel = (pdata[(xp>>3) + yw] & (0x80 >> (xp & 0x7)) );
drawPixel(x+xp, yyp, readPixel);
}
}
}
}
/***************************************************************************************
** Function name: pushImage
** Description: push 565 colour FLASH (PROGMEM) image into a defined area
*************************************************************************************x*/
void TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data)
{
#ifdef ESP32
pushImage(x, y, w, h, (uint16_t*) data);
#else
// Partitioned memory FLASH processor
if ((x >= _iwidth) || (y >= _iheight) || (w == 0) || (h == 0) || !_created) return;
if ((x + w < 0) || (y + h < 0)) return;
int32_t xo = 0;
int32_t yo = 0;
int32_t xs = x;
int32_t ys = y;
int32_t ws = w;
int32_t hs = h;
if (x < 0) { xo = -x; ws += x; xs = 0; }
if (y < 0) { yo = -y; hs += y; ys = 0; }
if (xs + ws >= (int32_t)_iwidth) ws = _iwidth - xs;
if (ys + hs >= (int32_t)_iheight) hs = _iheight - ys;
if (_bpp == 16) // Plot a 16 bpp image into a 16 bpp Sprite
{
for (int32_t yp = yo; yp < yo + hs; yp++)
{
x = xs;
for (int32_t xp = xo; xp < xo + ws; xp++)
{
uint16_t color = pgm_read_word(data + xp + yp * w);
if(!_iswapBytes) color = color<<8 | color>>8;
_img[x + ys * _iwidth] = color;
x++;
}
ys++;
}
}
else if (_bpp == 8) // Plot a 16 bpp image into a 8 bpp Sprite
{
for (int32_t yp = yo; yp < yo + hs; yp++)
{
x = xs;
for (int32_t xp = xo; xp < xo + ws; xp++)
{
uint16_t color = pgm_read_word(data + xp + yp * w);
if(_iswapBytes) color = color<<8 | color>>8;
_img8[x + ys * _iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3);
x++;
}
ys++;
}
}
else if (_bpp == 4)
{
#ifdef TFT_eSPI_DEBUG
Serial.println("TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data) not implemented");
#endif
return;
}
else // 1bpp
{
// Move coordinate rotation to support fn
if (_rotation == 1)
{
int32_t tx = x;
x = _dwidth - y - 1;
y = tx;
}
else if (_rotation == 2)
{
x = _dwidth - x - 1;
y = _dheight - y - 1;
}
else if (_rotation == 3)
{
int32_t tx = x;
x = y;
y = _dheight - tx - 1;
}
// Plot a 1bpp image into a 1bpp Sprite
const uint8_t* pdata = (const uint8_t* ) data;
uint32_t ww = (w+7) & 0xFFF8;
for (int32_t yp = 0; yp<h; yp++)
{
for (uint32_t xp = 0; xp<ww; xp+=8)
{
uint8_t pbyte = pgm_read_byte(pdata++);
for (uint8_t xc = 0; xc < 8; xc++)
{
if (xp+xc<(uint32_t)w) drawPixel(x+xp+xc, y+yp, (pbyte<<xc) & 0x80);
}
}
}
}
#endif // if ESP32 else ESP8266 check
}
/***************************************************************************************
** Function name: setSwapBytes
** Description: Used by 16 bit pushImage() to swap byte order in colours
***************************************************************************************/
void TFT_eSprite::setSwapBytes(bool swap)
{
_iswapBytes = swap;
}
/***************************************************************************************
** Function name: getSwapBytes
** Description: Return the swap byte order for colours
***************************************************************************************/
bool TFT_eSprite::getSwapBytes(void)
{
return _iswapBytes;
}
/***************************************************************************************
** Function name: setWindow
** Description: Set the bounds of a window for pushColor and writeColor
*************************************************************************************x*/
void TFT_eSprite::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1)
{
if (x0 > x1) swap_coord(x0, x1);
if (y0 > y1) swap_coord(y0, y1);
if ((x0 >= _iwidth) || (x1 < 0) || (y0 >= _iheight) || (y1 < 0))
{ // Point to that extra "off screen" pixel
_xs = 0;
_ys = _iheight;
_xe = 0;
_ye = _iheight;
}
else
{
if (x0 < 0) x0 = 0;
if (x1 >= _iwidth) x1 = _iwidth - 1;
if (y0 < 0) y0 = 0;
if (y1 >= _iheight) y1 = _iheight - 1;
_xs = x0;
_ys = y0;
_xe = x1;
_ye = y1;
}
_xptr = _xs;
_yptr = _ys;
}
/***************************************************************************************
** Function name: pushColor
** Description: Send a new pixel to the set window
*************************************************************************************x*/
void TFT_eSprite::pushColor(uint32_t color)
{
if (!_created ) return;
// Write the colour to RAM in set window
if (_bpp == 16)
_img [_xptr + _yptr * _iwidth] = (uint16_t) (color >> 8) | (color << 8);
else if (_bpp == 8)
_img8[_xptr + _yptr * _iwidth] = (uint8_t )((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3);
else if (_bpp == 4)
{
uint8_t c = (uint8_t)color & 0x0F;
if ((_xptr & 0x01) == 0) {
_img4[(_xptr + _yptr * _iwidth)>>1] = ((c << 4) & 0xF0) | (_img4[(_xptr + _yptr * _iwidth)>>1] & 0x0F); // new color is in bits 7 .. 4
}
else {
_img4[(_xptr - 1 + _yptr * _iwidth)>>1] = (_img4[(_xptr - 1 + _yptr * _iwidth)>>1] & 0xF0) | c; // new color is the low bits
}
}
else drawPixel(_xptr, _yptr, color);
// Increment x
_xptr++;
// Wrap on x and y to start, increment y if needed
if (_xptr > _xe)
{
_xptr = _xs;
_yptr++;
if (_yptr > _ye) _yptr = _ys;
}
}
/***************************************************************************************
** Function name: pushColor
** Description: Send a "len" new pixels to the set window
*************************************************************************************x*/
void TFT_eSprite::pushColor(uint32_t color, uint16_t len)
{
if (!_created ) return;
uint16_t pixelColor;
if (_bpp == 16)
pixelColor = (uint16_t) (color >> 8) | (color << 8);
else if (_bpp == 8)
pixelColor = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
else pixelColor = (uint16_t) color; // for 1bpp or 4bpp
while(len--) writeColor(pixelColor);
}
/***************************************************************************************
** Function name: writeColor
** Description: Write a pixel with pre-formatted colour to the set window
*************************************************************************************x*/
void TFT_eSprite::writeColor(uint16_t color)
{
if (!_created ) return;
// Write 16 bit RGB 565 encoded colour to RAM
if (_bpp == 16) _img [_xptr + _yptr * _iwidth] = color;
// Write 8 bit RGB 332 encoded colour to RAM
else if (_bpp == 8) _img8[_xptr + _yptr * _iwidth] = (uint8_t) color;
else if (_bpp == 4)
{
uint8_t c = (uint8_t)color & 0x0F;
if ((_xptr & 0x01) == 0)
_img4[(_xptr + _yptr * _iwidth)>>1] = ((c << 4) & 0xF0) | (_img4[(_xptr + _yptr * _iwidth)>>1] & 0x0F); // new color is in bits 7 .. 4
else
_img4[(_xptr - 1 + _yptr * _iwidth)>>1] = (_img4[(_xptr - 1 + _yptr * _iwidth)>>1] & 0xF0) | c; // new color is the low bits (x is odd)
}
else drawPixel(_xptr, _yptr, color);
// Increment x
_xptr++;
// Wrap on x and y to start, increment y if needed
if (_xptr > _xe)
{
_xptr = _xs;
_yptr++;
if (_yptr > _ye) _yptr = _ys;
}
}
/***************************************************************************************
** Function name: setScrollRect
** Description: Set scroll area within the sprite and the gap fill colour
*************************************************************************************x*/
void TFT_eSprite::setScrollRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t color)
{
if ((x >= _iwidth) || (y >= _iheight) || !_created ) return;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if ((x + w) > _iwidth ) w = _iwidth - x;
if ((y + h) > _iheight) h = _iheight - y;
if ( w < 1 || h < 1) return;
_sx = x;
_sy = y;
_sw = w;
_sh = h;
_scolor = color;
}
/***************************************************************************************
** Function name: scroll
** Description: Scroll dx,dy pixels, positive right,down, negative left,up
*************************************************************************************x*/
void TFT_eSprite::scroll(int16_t dx, int16_t dy)
{
if (abs(dx) >= _sw || abs(dy) >= _sh)
{
fillRect (_sx, _sy, _sw, _sh, _scolor);
return;
}
// Fetch the scroll area width and height set by setScrollRect()
uint32_t w = _sw - abs(dx); // line width to copy
uint32_t h = _sh - abs(dy); // lines to copy
int32_t iw = _iwidth; // width of sprite
// Fetch the x,y origin set by setScrollRect()
uint32_t tx = _sx; // to x
uint32_t fx = _sx; // from x
uint32_t ty = _sy; // to y
uint32_t fy = _sy; // from y
// Adjust for x delta
if (dx <= 0) fx -= dx;
else tx += dx;
// Adjust for y delta
if (dy <= 0) fy -= dy;
else
{ // Scrolling down so start copy from bottom
ty = ty + _sh - 1; // "To" pointer
iw = -iw; // Pointer moves backwards
fy = ty - dy; // "From" pointer
}
// Calculate "from y" and "to y" pointers in RAM
uint32_t fyp = fx + fy * _iwidth;
uint32_t typ = tx + ty * _iwidth;
// Now move the pixels in RAM
if (_bpp == 16)
{
while (h--)
{ // move pixel lines (to, from, byte count)
memmove( _img + typ, _img + fyp, w<<1);
typ += iw;
fyp += iw;
}
}
else if (_bpp == 8)
{
while (h--)
{ // move pixel lines (to, from, byte count)
memmove( _img8 + typ, _img8 + fyp, w);
typ += iw;
fyp += iw;
}
}
else if (_bpp == 4)
{
// could optimize for scrolling by even # pixels using memove (later)
if (dx > 0) { tx += w; fx += w; } // Start from right edge
while (h--)
{ // move pixels one by one
for (uint16_t xp = 0; xp < w; xp++)
{
if (dx <= 0) drawPixel(tx + xp, ty, readPixelValue(fx + xp, fy));
if (dx > 0) drawPixel(tx - xp, ty, readPixelValue(fx - xp, fy));
}
if (dy <= 0) { ty++; fy++; }
else { ty--; fy--; }
}
}
else if (_bpp == 1 )
{
if (dx > 0) { tx += w; fx += w; } // Start from right edge
while (h--)
{ // move pixels one by one
for (uint16_t xp = 0; xp < w; xp++)
{
if (dx <= 0) drawPixel(tx + xp, ty, readPixel(fx + xp, fy));
if (dx > 0) drawPixel(tx - xp, ty, readPixel(fx - xp, fy));
}
if (dy <= 0) { ty++; fy++; }
else { ty--; fy--; }
}
}
else return; // Not 1, 4, 8 or 16 bpp
// Fill the gap left by the scrolling
if (dx > 0) fillRect(_sx, _sy, dx, _sh, _scolor);
if (dx < 0) fillRect(_sx + _sw + dx, _sy, -dx, _sh, _scolor);
if (dy > 0) fillRect(_sx, _sy, _sw, dy, _scolor);
if (dy < 0) fillRect(_sx, _sy + _sh + dy, _sw, -dy, _scolor);
}
/***************************************************************************************
** Function name: fillSprite
** Description: Fill the whole sprite with defined colour
*************************************************************************************x*/
void TFT_eSprite::fillSprite(uint32_t color)
{
if (!_created ) return;
// Use memset if possible as it is super fast
if(( (uint8_t)color == (uint8_t)(color>>8) ) && _bpp == 16)
memset(_img, (uint8_t)color, _iwidth * _iheight * 2);
else if (_bpp == 8)
{
color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
memset(_img8, (uint8_t)color, _iwidth * _iheight);
}
else if (_bpp == 4)
{
uint8_t c = ((color & 0x0F) | (((color & 0x0F) << 4) & 0xF0));
memset(_img4, c, (_iwidth * _iheight) >> 1);
}
else if (_bpp == 1)
{
if(color) memset(_img8, 0xFF, (_iwidth>>3) * _iheight + 1);
else memset(_img8, 0x00, (_iwidth>>3) * _iheight + 1);
}
else fillRect(0, 0, _iwidth, _iheight, color);
}
/***************************************************************************************
** Function name: setCursor
** Description: Set the sprite text cursor x,y position
*************************************************************************************x*/
// Not needed - using TFT_eSPI class function and this->cursor_x/y
//void TFT_eSprite::setCursor(int16_t x, int16_t y)
//{
// this->cursor_x = x;
// this->cursor_y = y;
//}
/***************************************************************************************
** Function name: width
** Description: Return the width of sprite
*************************************************************************************x*/
// Return the size of the display
int16_t TFT_eSprite::width(void)
{
if (!_created ) return 0;
if (_bpp > 1) return _iwidth;
if (_rotation == 1 || _rotation == 3) return _dheight;
return _dwidth;
}
/***************************************************************************************
** Function name: height
** Description: Return the height of sprite
*************************************************************************************x*/
int16_t TFT_eSprite::height(void)
{
if (!_created ) return 0;
if (_bpp > 1) return _iheight;
if (_rotation == 1 || _rotation == 3) return _dwidth;
return _dheight;
}
/***************************************************************************************
** Function name: setRotation
** Description: Rotate coordinate frame for 1bpp sprite
*************************************************************************************x*/
// Does nothing for 8 and 16 bpp sprites. TODO allow rotation of these sprites
void TFT_eSprite::setRotation(uint8_t rotation)
{
if (_bpp != 1) return;
_rotation = rotation;
if (rotation == 0 && _iwidth > _iheight) swap_coord(_iwidth, _iheight);
if (rotation == 1 && _iwidth < _iheight) swap_coord(_iwidth, _iheight);
if (rotation == 2 && _iwidth > _iheight) swap_coord(_iwidth, _iheight);
if (rotation == 3 && _iwidth < _iheight) swap_coord(_iwidth, _iheight);
}
/***************************************************************************************
** Function name: getRotation
** Description: Get rotation for 1bpp sprite
*************************************************************************************x*/
uint8_t TFT_eSprite::getRotation(void)
{
return _rotation;
}
/***************************************************************************************
** Function name: drawPixel
** Description: push a single pixel at an arbitrary position
*************************************************************************************x*/
void TFT_eSprite::drawPixel(int32_t x, int32_t y, uint32_t color)
{
// Range checking
if ((x < 0) || (y < 0) || !_created) return;
if ((x >= _iwidth) || (y >= _iheight)) return;
if (_bpp == 16)
{
color = (color >> 8) | (color << 8);
_img[x+y*_iwidth] = (uint16_t) color;
}
else if (_bpp == 8)
{
_img8[x+y*_iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3);
}
else if (_bpp == 4)
{
uint8_t c = color & 0x0F;
int index = 0;
if ((x & 0x01) == 0) {
index = (x+y*_iwidth)>>1;
_img4[index] = (uint8_t)(((c << 4) & 0xF0) | (_img4[index] & 0x0F));
}
else {
index = (x-1+y*_iwidth)>>1;
_img4[index] = (uint8_t)(c | (_img4[index] & 0xF0));
}
}
else // 1 bpp
{
if (_rotation == 1)
{
uint16_t tx = x;
x = _dwidth - y - 1;
y = tx;
}
else if (_rotation == 2)
{
x = _dwidth - x - 1;
y = _dheight - y - 1;
}
else if (_rotation == 3)
{
uint16_t tx = x;
x = y;
y = _dheight - tx - 1;
}
if (color) _img8[(x + y * _bitwidth)>>3] |= (0x80 >> (x & 0x7));
else _img8[(x + y * _bitwidth)>>3] &= ~(0x80 >> (x & 0x7));
}
}
/***************************************************************************************
** Function name: drawLine
** Description: draw a line between 2 arbitrary points
*************************************************************************************x*/
void TFT_eSprite::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color)
{
if (!_created ) return;
bool steep = abs(y1 - y0) > abs(x1 - x0);
if (steep) {
swap_coord(x0, y0);
swap_coord(x1, y1);
}
if (x0 > x1) {
swap_coord(x0, x1);
swap_coord(y0, y1);
}
int32_t dx = x1 - x0, dy = abs(y1 - y0);;
int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0;
if (y0 < y1) ystep = 1;
// Split into steep and not steep for FastH/V separation
if (steep) {
for (; x0 <= x1; x0++) {
dlen++;
err -= dy;
if (err < 0) {
err += dx;
if (dlen == 1) drawPixel(y0, xs, color);
else drawFastVLine(y0, xs, dlen, color);
dlen = 0; y0 += ystep; xs = x0 + 1;
}
}
if (dlen) drawFastVLine(y0, xs, dlen, color);
}
else
{
for (; x0 <= x1; x0++) {
dlen++;
err -= dy;
if (err < 0) {
err += dx;
if (dlen == 1) drawPixel(xs, y0, color);
else drawFastHLine(xs, y0, dlen, color);
dlen = 0; y0 += ystep; xs = x0 + 1;
}
}
if (dlen) drawFastHLine(xs, y0, dlen, color);
}
}
/***************************************************************************************
** Function name: drawFastVLine
** Description: draw a vertical line
*************************************************************************************x*/
void TFT_eSprite::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color)
{
if ((x < 0) || (x >= _iwidth) || (y >= _iheight) || !_created) return;
if (y < 0) { h += y; y = 0; }
if ((y + h) > _iheight) h = _iheight - y;
if (h < 1) return;
if (_bpp == 16)
{
color = (color >> 8) | (color << 8);
int32_t yp = x + _iwidth * y;
while (h--) {_img[yp] = (uint16_t) color; yp += _iwidth;}
}
else if (_bpp == 8)
{
color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
while (h--) _img8[x + _iwidth * y++] = (uint8_t) color;
}
else if (_bpp == 4)
{
if ((x & 0x01) == 0)
{
uint8_t c = (uint8_t) (color & 0xF) << 4;
while (h--) {
_img4[(x + _iwidth * y)>>1] = (uint8_t) (c | _img4[(x + _iwidth * y)>>1] & 0x0F);
y++;
}
}
else {
uint8_t c = (uint8_t)color & 0xF;
while (h--) {
_img4[(x - 1 + _iwidth * y)>>1] = (uint8_t) (c | _img4[(x - 1 + _iwidth * y)>>1] & 0xF0); // x is odd; new color goes into the low bits.
y++;
}
}
}
else
{
while (h--)
{
drawPixel(x, y, color);
y++;
}
}
}
/***************************************************************************************
** Function name: drawFastHLine
** Description: draw a horizontal line
*************************************************************************************x*/
void TFT_eSprite::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color)
{
if ((y < 0) || (x >= _iwidth) || (y >= _iheight) || !_created) return;
if (x < 0) { w += x; x = 0; }
if ((x + w) > _iwidth) w = _iwidth - x;
if (w < 1) return;
if (_bpp == 16)
{
color = (color >> 8) | (color << 8);
while (w--) _img[_iwidth * y + x++] = (uint16_t) color;
}
else if (_bpp == 8)
{
color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
memset(_img8+_iwidth * y + x, (uint8_t)color, w);
}
else if (_bpp == 4)
{
uint8_t c = (uint8_t)color & 0x0F;
uint8_t c2 = (c | ((c << 4) & 0xF0));
if ((x & 0x01) == 1)
{
drawPixel(x, y, color);
x++; w--;
if (w < 1)
return;
}
if (((w + x) & 0x01) == 1)
{
// handle the extra one at the other end
drawPixel(x + w - 1, y, color);
w--;
if (w < 1) return;
}
memset(_img4 + ((_iwidth * y + x) >> 1), c2, (w >> 1));
}
else {
while (w--)
{
drawPixel(x, y, color);
x++;
}
}
}
/***************************************************************************************
** Function name: fillRect
** Description: draw a filled rectangle
*************************************************************************************x*/
void TFT_eSprite::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color)
{
if (!_created ) return;
if ((x >= _iwidth) || (y >= _iheight)) return;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if ((x + w) > _iwidth) w = _iwidth - x;
if ((y + h) > _iheight) h = _iheight - y;
if ((w < 1) || (h < 1)) return;
int32_t yp = _iwidth * y + x;
if (_bpp == 16)
{
color = (color >> 8) | (color << 8);
uint32_t iw = w;
int32_t ys = yp;
if(h--) {while (iw--) _img[yp++] = (uint16_t) color;}
yp = ys;
while (h--)
{
yp += _iwidth;
memcpy( _img+yp, _img+ys, w<<1);
}
}
else if (_bpp == 8)
{
color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
while (h--)
{
memset(_img8 + yp, (uint8_t)color, w);
yp += _iwidth;
}
}
else if (_bpp == 4)
{
uint8_t c1 = (uint8_t)color & 0x0F;
uint8_t c2 = c1 | ((c1 << 4) & 0xF0);
if ((x & 0x01) == 0 && (w & 0x01) == 0)
{
yp = (yp >> 1);
while (h--)
{
memset(_img4 + yp, c2, (w>>1));
yp += (_iwidth >> 1);
}
}
else if ((x & 0x01) == 0)
{
// same as above but you have a hangover on the right.
yp = (yp >> 1);
while (h--)
{
if (w > 1)
memset(_img4 + yp, c2, (w-1)>>1);
// handle the rightmost pixel by calling drawPixel
drawPixel(x+w-1, y+h, c1);
yp += (_iwidth >> 1);
}
}
else if ((w & 0x01) == 1)
{
yp = (yp + 1) >> 1;
while (h--) {
drawPixel(x, y+h-1, color & 0x0F);
if (w > 1)
memset(_img4 + (yp + ((x-1)>>1)), c2, (w-1)>>1);
// same as above but you have a hangover on the left instead
yp += (_iwidth >> 1);
}
}
else
{
yp = (yp + 1) >> 1;
while (h--) {
drawPixel(x, y+h-1, color & 0x0F);
drawPixel(x+w-1, y+h-1, color & 0x0F);
if (w > 2)
memset(_img4 + (yp + ((x-1)>>1)), c2, (w-2)>>1);
// maximal hacking, single pixels on left and right.
yp += (_iwidth >> 1);
}
}
}
else
{
while (h--)
{
int32_t ww = w;
int32_t xx = x;
while (ww--) drawPixel(xx++, y, color);
y++;
}
}
}
/***************************************************************************************
** Function name: write
** Description: draw characters piped through serial stream
*************************************************************************************x*/
size_t TFT_eSprite::write(uint8_t utf8)
{
uint16_t uniCode = decodeUTF8(utf8);
if (!uniCode) return 1;
if (utf8 == '\r') return 1;
#ifdef SMOOTH_FONT
if(this->fontLoaded)
{
if (uniCode < 32 && utf8 != '\n') return 1;
//fontFile = SPIFFS.open( _gFontFilename, "r" );
//fontFile = SPIFFS.open( this->_gFontFilename, "r" );
//if(!fontFile)
//{
// fontLoaded = false;
// return 1;
//}
//Serial.print("Decoded Unicode = 0x");Serial.println(unicode,HEX);
drawGlyph(uniCode);
//fontFile.close();
return 1;
}
#endif
if (!_created ) return 1;
if (uniCode == '\n') uniCode+=22; // Make it a valid space character to stop errors
else if (uniCode < 32) return 1;
uint16_t width = 0;
uint16_t height = 0;
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
//Serial.print((uint8_t) uniCode); // Debug line sends all printed TFT text to serial port
//Serial.println(uniCode, HEX); // Debug line sends all printed TFT text to serial port
//delay(5); // Debug optional wait for serial port to flush through
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#ifdef LOAD_GFXFF
if(!gfxFont) {
#endif
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#ifdef LOAD_FONT2
if (textfont == 2)
{
if (utf8 > 127) return 1;
width = pgm_read_byte(widtbl_f16 + uniCode-32);
height = chr_hgt_f16;
// Font 2 is rendered in whole byte widths so we must allow for this
width = (width + 6) / 8; // Width in whole bytes for font 2, should be + 7 but must allow for font width change
width = width * 8; // Width converted back to pixles
}
#ifdef LOAD_RLE
else
#endif
#endif
#ifdef LOAD_RLE
{
if ((textfont>2) && (textfont<9))
{
if (utf8 > 127) return 1;
// Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements
width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[textfont].widthtbl ) ) + uniCode-32 );
height= pgm_read_byte( &fontdata[textfont].height );
}
}
#endif
#ifdef LOAD_GLCD
if (textfont==1)
{
width = 6;
height = 8;
}
#else
if (textfont==1) return 1;
#endif
height = height * textsize;
if (utf8 == '\n')
{
this->cursor_y += height;
this->cursor_x = 0;
}
else
{
if (textwrapX && (this->cursor_x + width * textsize > _iwidth))
{
this->cursor_y += height;
this->cursor_x = 0;
}
if (textwrapY && (this->cursor_y >= _iheight)) this->cursor_y = 0;
this->cursor_x += drawChar(uniCode, this->cursor_x, this->cursor_y, textfont);
}
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#ifdef LOAD_GFXFF
} // Custom GFX font
else
{
if(utf8 == '\n') {
this->cursor_x = 0;
this->cursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance);
} else {
if (uniCode > pgm_read_word(&gfxFont->last )) return 1;
if (uniCode < pgm_read_word(&gfxFont->first)) return 1;
uint8_t c2 = uniCode - pgm_read_word(&gfxFont->first);
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
uint8_t w = pgm_read_byte(&glyph->width),
h = pgm_read_byte(&glyph->height);
if((w > 0) && (h > 0)) { // Is there an associated bitmap?
int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset);
if(textwrapX && ((this->cursor_x + textsize * (xo + w)) > _iwidth)) {
// Drawing character would go off right edge; wrap to new line
this->cursor_x = 0;
this->cursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance);
}
if (textwrapY && (this->cursor_y >= _iheight)) this->cursor_y = 0;
drawChar(this->cursor_x, this->cursor_y, uniCode, textcolor, textbgcolor, textsize);
}
this->cursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize;
}
}
#endif // LOAD_GFXFF
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
return 1;
}
/***************************************************************************************
** Function name: drawChar
** Description: draw a single character in the Adafruit GLCD or freefont
*************************************************************************************x*/
void TFT_eSprite::drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32_t bg, uint8_t size)
{
if (!_created ) return;
if ((x >= _iwidth) || // Clip right
(y >= _iheight) || // Clip bottom
((x + 6 * size - 1) < 0) || // Clip left
((y + 8 * size - 1) < 0)) // Clip top
return;
if (c < 32) return;
#ifdef LOAD_GLCD
//>>>>>>>>>>>>>>>>>>
#ifdef LOAD_GFXFF
if(!gfxFont) { // 'Classic' built-in font
#endif
//>>>>>>>>>>>>>>>>>>
bool fillbg = (bg != color);
if ((size==1) && fillbg)
{
uint8_t column[6];
uint8_t mask = 0x1;
for (int8_t i = 0; i < 5; i++ ) column[i] = pgm_read_byte(font + (c * 5) + i);
column[5] = 0;
int8_t j, k;
for (j = 0; j < 8; j++) {
for (k = 0; k < 5; k++ ) {
if (column[k] & mask) {
drawPixel(x + k, y + j, color);
}
else {
drawPixel(x + k, y + j, bg);
}
}
mask <<= 1;
drawPixel(x + k, y + j, bg);
}
}
else
{
for (int8_t i = 0; i < 6; i++ ) {
uint8_t line;
if (i == 5)
line = 0x0;
else
line = pgm_read_byte(font + (c * 5) + i);
if (size == 1) // default size
{
for (int8_t j = 0; j < 8; j++) {
if (line & 0x1) drawPixel(x + i, y + j, color);
line >>= 1;
}
}
else { // big size
for (int8_t j = 0; j < 8; j++) {
if (line & 0x1) fillRect(x + (i * size), y + (j * size), size, size, color);
else if (fillbg) fillRect(x + i * size, y + j * size, size, size, bg);
line >>= 1;
}
}
}
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
#ifdef LOAD_GFXFF
} else { // Custom font
#endif
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
#endif // LOAD_GLCD
#ifdef LOAD_GFXFF
// Filter out bad characters not present in font
if ((c >= pgm_read_word(&gfxFont->first)) && (c <= pgm_read_word(&gfxFont->last )))
{
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
c -= pgm_read_word(&gfxFont->first);
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]);
uint8_t *bitmap = (uint8_t *)pgm_read_dword(&gfxFont->bitmap);
uint32_t bo = pgm_read_word(&glyph->bitmapOffset);
uint8_t w = pgm_read_byte(&glyph->width),
h = pgm_read_byte(&glyph->height);
//xa = pgm_read_byte(&glyph->xAdvance);
int8_t xo = pgm_read_byte(&glyph->xOffset),
yo = pgm_read_byte(&glyph->yOffset);
uint8_t xx, yy, bits=0, bit=0;
int16_t xo16 = 0, yo16 = 0;
if(size > 1) {
xo16 = xo;
yo16 = yo;
}
uint16_t hpc = 0; // Horizontal foreground pixel count
for(yy=0; yy<h; yy++) {
for(xx=0; xx<w; xx++) {
if(bit == 0) {
bits = pgm_read_byte(&bitmap[bo++]);
bit = 0x80;
}
if(bits & bit) hpc++;
else {
if (hpc) {
if(size == 1) drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color);
else fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color);
hpc=0;
}
}
bit >>= 1;
}
// Draw pixels for this line as we are about to increment yy
if (hpc) {
if(size == 1) drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color);
else fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color);
hpc=0;
}
}
}
#endif
#ifdef LOAD_GLCD
#ifdef LOAD_GFXFF
} // End classic vs custom font
#endif
#endif
}
/***************************************************************************************
** Function name: drawChar
** Description: draw a unicode onto the screen
*************************************************************************************x*/
// Any UTF-8 decoding must be done before calling drawChar()
int16_t TFT_eSprite::drawChar(uint16_t uniCode, int32_t x, int32_t y)
{
return drawChar(uniCode, x, y, textfont);
}
// Any UTF-8 decoding must be done before calling drawChar()
int16_t TFT_eSprite::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font)
{
if (!_created ) return 0;
if (!uniCode) return 0;
if (font==1)
{
#ifdef LOAD_GLCD
#ifndef LOAD_GFXFF
drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
return 6 * textsize;
#endif
#else
#ifndef LOAD_GFXFF
return 0;
#endif
#endif
#ifdef LOAD_GFXFF
drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
if(!gfxFont) { // 'Classic' built-in font
#ifdef LOAD_GLCD
return 6 * textsize;
#else
return 0;
#endif
}
else
{
if((uniCode >= pgm_read_word(&gfxFont->first)) && (uniCode <= pgm_read_word(&gfxFont->last) ))
{
uint16_t c2 = uniCode - pgm_read_word(&gfxFont->first);
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
return pgm_read_byte(&glyph->xAdvance) * textsize;
}
else
{
return 0;
}
}
#endif
}
if ((font>1) && (font<9) && ((uniCode < 32) || (uniCode > 127))) return 0;
int32_t width = 0;
int32_t height = 0;
uint32_t flash_address = 0;
uniCode -= 32;
#ifdef LOAD_FONT2
if (font == 2)
{
// This is faster than using the fontdata structure
flash_address = pgm_read_dword(&chrtbl_f16[uniCode]);
width = pgm_read_byte(widtbl_f16 + uniCode);
height = chr_hgt_f16;
}
#ifdef LOAD_RLE
else
#endif
#endif
#ifdef LOAD_RLE
{
if ((font>2) && (font<9))
{
// This is slower than above but is more convenient for the RLE fonts
flash_address = pgm_read_dword( (const void*) (pgm_read_dword( &(fontdata[font].chartbl ) ) + uniCode*sizeof(void *)) );
width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[font].widthtbl ) ) + uniCode );
height= pgm_read_byte( &fontdata[font].height );
}
}
#endif
int32_t w = width;
int32_t pX = 0;
int32_t pY = y;
uint8_t line = 0;
#ifdef LOAD_FONT2 // chop out code if we do not need it
if (font == 2) {
w = w + 6; // Should be + 7 but we need to compensate for width increment
w = w / 8;
if (x + width * textsize >= _iwidth) return width * textsize ;
for (int32_t i = 0; i < height; i++)
{
if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize, textbgcolor);
for (int32_t k = 0; k < w; k++)
{
line = pgm_read_byte((uint8_t *)flash_address + w * i + k);
if (line) {
if (textsize == 1) {
pX = x + k * 8;
if (line & 0x80) drawPixel(pX, pY, textcolor);
if (line & 0x40) drawPixel(pX + 1, pY, textcolor);
if (line & 0x20) drawPixel(pX + 2, pY, textcolor);
if (line & 0x10) drawPixel(pX + 3, pY, textcolor);
if (line & 0x08) drawPixel(pX + 4, pY, textcolor);
if (line & 0x04) drawPixel(pX + 5, pY, textcolor);
if (line & 0x02) drawPixel(pX + 6, pY, textcolor);
if (line & 0x01) drawPixel(pX + 7, pY, textcolor);
}
else {
pX = x + k * 8 * textsize;
if (line & 0x80) fillRect(pX, pY, textsize, textsize, textcolor);
if (line & 0x40) fillRect(pX + textsize, pY, textsize, textsize, textcolor);
if (line & 0x20) fillRect(pX + 2 * textsize, pY, textsize, textsize, textcolor);
if (line & 0x10) fillRect(pX + 3 * textsize, pY, textsize, textsize, textcolor);
if (line & 0x08) fillRect(pX + 4 * textsize, pY, textsize, textsize, textcolor);
if (line & 0x04) fillRect(pX + 5 * textsize, pY, textsize, textsize, textcolor);
if (line & 0x02) fillRect(pX + 6 * textsize, pY, textsize, textsize, textcolor);
if (line & 0x01) fillRect(pX + 7 * textsize, pY, textsize, textsize, textcolor);
}
}
}
pY += textsize;
}
}
#ifdef LOAD_RLE
else
#endif
#endif //FONT2
#ifdef LOAD_RLE //674 bytes of code
// Font is not 2 and hence is RLE encoded
{
w *= height; // Now w is total number of pixels in the character
if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize * height, textbgcolor);
int16_t color = textcolor;
if (_bpp == 16) color = (textcolor >> 8) | (textcolor << 8);
else if (_bpp == 8) color = ((textcolor & 0xE000)>>8 | (textcolor & 0x0700)>>6 | (textcolor & 0x0018)>>3);
int32_t px = 0, py = pY; // To hold character block start and end column and row values
int32_t pc = 0; // Pixel count
uint8_t np = textsize * textsize; // Number of pixels in a drawn pixel
uint8_t tnp = 0; // Temporary copy of np for while loop
uint8_t ts = textsize - 1; // Temporary copy of textsize
// 16 bit pixel count so maximum font size is equivalent to 180x180 pixels in area
// w is total number of pixels to plot to fill character block
while (pc < w)
{
line = pgm_read_byte((uint8_t *)flash_address);
flash_address++; // 20 bytes smaller by incrementing here
if (line & 0x80) {
line &= 0x7F;
line++;
if (ts) {
px = x + textsize * (pc % width); // Keep these px and py calculations outside the loop as they are slow
py = y + textsize * (pc / width);
}
else {
px = x + pc % width; // Keep these px and py calculations outside the loop as they are slow
py = y + pc / width;
}
while (line--) {
pc++;
setWindow(px, py, px + ts, py + ts);
if (ts) { tnp = np; while (tnp--) writeColor(color); }
else writeColor(color);
px += textsize;
if (px >= (x + width * textsize))
{
px = x;
py += textsize;
}
}
}
else {
line++;
pc += line;
}
}
}
// End of RLE font rendering
#endif
return width * textsize; // x +
}
#ifdef SMOOTH_FONT
/***************************************************************************************
** Function name: drawGlyph
** Description: Write a character to the sprite cursor position
*************************************************************************************x*/
void TFT_eSprite::drawGlyph(uint16_t code)
{
if (code < 0x21)
{
if (code == 0x20) {
if (_created) this->cursor_x += this->gFont.spaceWidth;
else this->cursor_x += this->gFont.spaceWidth;
return;
}
if (code == '\n') {
if (_created)
{
this->cursor_x = 0;
this->cursor_y += this->gFont.yAdvance;
if (this->cursor_y >= _height) this->cursor_y = 0;
return;
}
else
{
cursor_x = 0;
cursor_y += gFont.yAdvance;
if (cursor_y >= _height) cursor_y = 0;
return;
}
}
}
uint16_t gNum = 0;
bool found = this->getUnicodeIndex(code, &gNum);
uint16_t fg = this->textcolor;
uint16_t bg = this->textbgcolor;
if (found)
{
bool newSprite = !_created;
if (newSprite)
{
createSprite(this->gWidth[gNum], this->gFont.yAdvance);
if(bg) fillSprite(bg);
this->cursor_x = -this->gdX[gNum];
this->cursor_y = 0;
}
this->fontFile.seek(this->gBitmap[gNum], fs::SeekSet); // This is slow for a significant position shift!
uint8_t pbuffer[this->gWidth[gNum]];
int16_t xs = 0;
uint16_t dl = 0;
for (int32_t y = 0; y < this->gHeight[gNum]; y++)
{
this->fontFile.read(pbuffer, this->gWidth[gNum]);
for (int32_t x = 0; x < this->gWidth[gNum]; x++)
{
uint8_t pixel = pbuffer[x];
if (pixel)
{
if (pixel != 0xFF)
{
if (dl) { drawFastHLine( xs, y + this->cursor_y + this->gFont.maxAscent - this->gdY[gNum], dl, fg); dl = 0; }
if (_bpp != 1) drawPixel(x + this->cursor_x + this->gdX[gNum], y + this->cursor_y + this->gFont.maxAscent - this->gdY[gNum], alphaBlend(pixel, fg, bg));
else if (pixel>127) drawPixel(x + this->cursor_x + this->gdX[gNum], y + this->cursor_y + this->gFont.maxAscent - this->gdY[gNum], fg);
}
else
{
if (dl==0) xs = x + this->cursor_x + this->gdX[gNum];
dl++;
}
}
else
{
if (dl) { drawFastHLine( xs, y + this->cursor_y + this->gFont.maxAscent - this->gdY[gNum], dl, fg); dl = 0; }
}
}
if (dl) { drawFastHLine( xs, y + this->cursor_y + this->gFont.maxAscent - this->gdY[gNum], dl, fg); dl = 0; }
}
if (newSprite)
{
pushSprite(this->cursor_x + this->gdX[gNum], this->cursor_y, bg);
deleteSprite();
this->cursor_x += this->gxAdvance[gNum];
}
else this->cursor_x += this->gxAdvance[gNum];
}
else
{
// Not a Unicode in font so draw a rectangle and move on cursor
drawRect(this->cursor_x, this->cursor_y + this->gFont.maxAscent - this->gFont.ascent, this->gFont.spaceWidth, this->gFont.ascent, fg);
this->cursor_x += this->gFont.spaceWidth + 1;
}
}
/***************************************************************************************
** Function name: printToSprite
** Description: Write a string to the sprite cursor position
*************************************************************************************x*/
void TFT_eSprite::printToSprite(String string)
{
if(!this->fontLoaded) return;
uint16_t len = string.length();
char cbuffer[len + 1]; // Add 1 for the null
string.toCharArray(cbuffer, len + 1); // Add 1 for the null, otherwise characters get dropped
printToSprite(cbuffer, len);
//printToSprite((char*)string.c_str(), string.length());
}
/***************************************************************************************
** Function name: printToSprite
** Description: Write a string to the sprite cursor position
*************************************************************************************x*/
void TFT_eSprite::printToSprite(char *cbuffer, uint16_t len) //String string)
{
if(!this->fontLoaded) return;
//fontFile = SPIFFS.open( this->_gFontFilename, "r" );
if(!this->fontFile)
{
this->fontLoaded = false;
return;
}
uint16_t n = 0;
bool newSprite = !_created;
if (newSprite)
{
int16_t sWidth = 0;
uint16_t index = 0;
while (n < len)
{
uint16_t unicode = decodeUTF8((uint8_t*)cbuffer, &n, len - n);
if (this->getUnicodeIndex(unicode, &index))
{
if (n == 0) sWidth -= this->gdX[index];
if (n == len-1) sWidth += ( this->gWidth[index] + this->gdX[index]);
else sWidth += this->gxAdvance[index];
}
else sWidth += this->gFont.spaceWidth + 1;
}
createSprite(sWidth, this->gFont.yAdvance);
if (this->textbgcolor != TFT_BLACK) fillSprite(this->textbgcolor);
}
n = 0;
while (n < len)
{
uint16_t unicode = decodeUTF8((uint8_t*)cbuffer, &n, len - n);
//Serial.print("Decoded Unicode = 0x");Serial.println(unicode,HEX);
//Serial.print("n = ");Serial.println(n);
drawGlyph(unicode);
}
if (newSprite)
{ // The sprite had to be created so place at TFT cursor
pushSprite(_tft->cursor_x, _tft->cursor_y);
deleteSprite();
}
//fontFile.close();
}
/***************************************************************************************
** Function name: printToSprite
** Description: Print character in a Sprite, create sprite if needed
*************************************************************************************x*/
int16_t TFT_eSprite::printToSprite(int16_t x, int16_t y, uint16_t index)
{
bool newSprite = !_created;
int16_t sWidth = this->gWidth[index];
if (newSprite)
{
createSprite(sWidth, this->gFont.yAdvance);
if (this->textbgcolor != TFT_BLACK) fillSprite(this->textbgcolor);
drawGlyph(this->gUnicode[index]);
pushSprite(x + this->gdX[index], y, this->textbgcolor);
deleteSprite();
}
else drawGlyph(this->gUnicode[index]);
return this->gxAdvance[index];
}
#endif