mirror of
https://github.com/Bodmer/TFT_eSPI.git
synced 2025-08-04 13:14:46 +02:00
Add ability to plot Sprites with a "transparent" colour
Can now specify a colour that will not be plotted so some part of the Sprite are "transparent" and the TFT backforund shows through. New example added for demonstration.
This commit is contained in:
188
TFT_eSPI.cpp
188
TFT_eSPI.cpp
@@ -387,7 +387,7 @@ void TFT_eSPI::writedata(uint8_t c)
|
||||
** Function name: readcommand8 (for ILI9341 Interface II i.e. IM [3:0] = "1101")
|
||||
** Description: Read a 8 bit data value from an indexed command register
|
||||
***************************************************************************************/
|
||||
uint8_t TFT_eSPI::readcommand8(uint8_t cmd_function, uint8_t index)
|
||||
uint8_t TFT_eSPI::readcommand8(uint8_t cmd_function, uint8_t index)
|
||||
{
|
||||
spi_begin();
|
||||
index = 0x10 + (index & 0x0F);
|
||||
@@ -415,7 +415,7 @@ void TFT_eSPI::writedata(uint8_t c)
|
||||
** Function name: readcommand16 (for ILI9341 Interface II i.e. IM [3:0] = "1101")
|
||||
** Description: Read a 16 bit data value from an indexed command register
|
||||
***************************************************************************************/
|
||||
uint16_t TFT_eSPI::readcommand16(uint8_t cmd_function, uint8_t index)
|
||||
uint16_t TFT_eSPI::readcommand16(uint8_t cmd_function, uint8_t index)
|
||||
{
|
||||
uint32_t reg = 0;
|
||||
reg |= (readcommand8(cmd_function, index + 0) << 8);
|
||||
@@ -429,7 +429,7 @@ void TFT_eSPI::writedata(uint8_t c)
|
||||
** Function name: readcommand32 (for ILI9341 Interface II i.e. IM [3:0] = "1101")
|
||||
** Description: Read a 32 bit data value from an indexed command register
|
||||
***************************************************************************************/
|
||||
uint32_t TFT_eSPI::readcommand32(uint8_t cmd_function, uint8_t index)
|
||||
uint32_t TFT_eSPI::readcommand32(uint8_t cmd_function, uint8_t index)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
@@ -472,7 +472,7 @@ uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0)
|
||||
** Function name: read rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
|
||||
** Description: Read 565 pixel colours from a defined area
|
||||
***************************************************************************************/
|
||||
void TFT_eSPI::readRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t *data)
|
||||
void TFT_eSPI::readRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t *data)
|
||||
{
|
||||
if ((x > _width) || (y > _height) || (w == 0) || (h == 0)) return;
|
||||
|
||||
@@ -510,7 +510,7 @@ uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0)
|
||||
** Function name: push rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
|
||||
** Description: push 565 pixel colours into a defined area
|
||||
***************************************************************************************/
|
||||
void TFT_eSPI::pushRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t *data)
|
||||
void TFT_eSPI::pushRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t *data)
|
||||
{
|
||||
if ((x >= _width) || (y >= _height) || (w == 0) || (h == 0)) return;
|
||||
|
||||
@@ -538,7 +538,7 @@ uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0)
|
||||
** Function name: push sprite
|
||||
** Description: plot 16 bit sprite in a defined area with clipping
|
||||
***************************************************************************************/
|
||||
void TFT_eSPI::pushSprite(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t *data)
|
||||
void TFT_eSPI::pushSprite(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t *data)
|
||||
{
|
||||
|
||||
if ((x >= (int32_t)_width) || (y >= (int32_t)_height)) return;
|
||||
@@ -586,12 +586,68 @@ uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0)
|
||||
spi_end();
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: push sprite
|
||||
** Description: plot 16 bit sprite with 1 colour being transparent
|
||||
***************************************************************************************/
|
||||
void TFT_eSPI::pushSprite(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t *data, uint16_t transp)
|
||||
{
|
||||
|
||||
if ((x >= (int32_t)_width) || (y >= (int32_t)_height)) return;
|
||||
|
||||
int32_t dx = 0;
|
||||
int32_t dy = 0;
|
||||
int32_t dw = w;
|
||||
int32_t dh = h;
|
||||
|
||||
if (x < 0) { dw += x; dx = -x; x = 0; }
|
||||
if (y < 0) { dh += y; dy = -y; y = 0; }
|
||||
|
||||
if ((x + w) > _width ) dw = _width - x;
|
||||
if ((y + h) > _height) dh = _height - y;
|
||||
|
||||
if (dw < 1 || dh < 1) return;
|
||||
|
||||
spi_begin();
|
||||
|
||||
data += dx + dy * w;
|
||||
|
||||
int32_t xe = x + dw - 1, ye = y + dh - 1;
|
||||
|
||||
while (dh--)
|
||||
{
|
||||
int32_t len = dw;
|
||||
uint16_t* ptr = data;
|
||||
int32_t px = x;
|
||||
boolean move = true;
|
||||
|
||||
while (len--)
|
||||
{
|
||||
if (transp != *ptr)
|
||||
{
|
||||
if (move) { move = false; setAddrWindow(px, y, xe, ye); }
|
||||
SPI.write16(*ptr>>8 | *ptr <<8);
|
||||
}
|
||||
else move = true;
|
||||
px++;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
y++;
|
||||
data += w;
|
||||
}
|
||||
|
||||
CS_H;
|
||||
|
||||
spi_end();
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: push sprite
|
||||
** Description: plot 8 bit sprite with clipping using a line buffer
|
||||
***************************************************************************************/
|
||||
void TFT_eSPI::pushSprite(int32_t x, int32_t y, uint32_t w, uint32_t h, uint8_t *data)
|
||||
void TFT_eSPI::pushSprite(int32_t x, int32_t y, uint32_t w, uint32_t h, uint8_t *data)
|
||||
{
|
||||
if ((x >= (int32_t)_width) || (y >= (int32_t)_height)) return;
|
||||
|
||||
@@ -658,12 +714,85 @@ uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0)
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: push sprite
|
||||
** Description: plot 8 bit sprite with 1 colour being transparent
|
||||
***************************************************************************************/
|
||||
void TFT_eSPI::pushSprite(int32_t x, int32_t y, uint32_t w, uint32_t h, uint8_t *data, uint8_t transp)
|
||||
{
|
||||
if ((x >= (int32_t)_width) || (y >= (int32_t)_height)) return;
|
||||
|
||||
int32_t dx = 0;
|
||||
int32_t dy = 0;
|
||||
int32_t dw = w;
|
||||
int32_t dh = h;
|
||||
|
||||
if (x < 0) { dw += x; dx = -x; x = 0; }
|
||||
if (y < 0) { dh += y; dy = -y; y = 0; }
|
||||
|
||||
if ((x + w) > _width ) dw = _width - x;
|
||||
if ((y + h) > _height) dh = _height - y;
|
||||
|
||||
if (dw < 1 || dh < 1) return;
|
||||
|
||||
spi_begin();
|
||||
|
||||
data += dx + dy * w;
|
||||
|
||||
int32_t xe = x + dw - 1, ye = y + dh - 1;
|
||||
|
||||
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
|
||||
|
||||
_lastColor = -1; // Set to illegal value
|
||||
|
||||
// Used to store last shifted colour
|
||||
uint16_t color565 = 0;
|
||||
|
||||
while (dh--)
|
||||
{
|
||||
int32_t len = dw;
|
||||
uint8_t* ptr = data; //<<<<<<<<< changed to 8 bit
|
||||
int32_t px = x;
|
||||
boolean move = true;
|
||||
|
||||
while (len--)
|
||||
{
|
||||
if (transp != *ptr)
|
||||
{
|
||||
if (move) { move = false; setAddrWindow(px, y, xe, ye); }
|
||||
uint32_t color = *ptr;
|
||||
|
||||
// Shifts are slow so check if colour has changed first
|
||||
if (color != _lastColor) {
|
||||
// =====Green===== ===============Red==============
|
||||
color565 = (color & 0x1C)<<6 | (color & 0xC0)<<5 | (color & 0xE0)<<8
|
||||
// =====Green===== =======Blue======
|
||||
| (color & 0x1C)<<3 | blue[color & 0x03];
|
||||
_lastColor = color;
|
||||
}
|
||||
SPI.write16(color565);
|
||||
}
|
||||
else move = true;
|
||||
px++;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
y++;
|
||||
data += w;
|
||||
}
|
||||
|
||||
CS_H;
|
||||
|
||||
spi_end();
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: read rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
|
||||
** Description: Read RGB pixel colours from a defined area
|
||||
***************************************************************************************/
|
||||
// If w and h are 1, then 1 pixel is read, *data array size must be 3 bytes per pixel
|
||||
void TFT_eSPI::readRectRGB(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t *data)
|
||||
void TFT_eSPI::readRectRGB(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t *data)
|
||||
{
|
||||
spi_begin();
|
||||
|
||||
@@ -4301,7 +4430,7 @@ void TFT_eSprite::deleteSprite(void)
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: pushSprite
|
||||
** Description: Delete the sprite to free up memory (RAM)
|
||||
** Description: Push the sprite to the TFT at x, y
|
||||
*************************************************************************************x*/
|
||||
void TFT_eSprite::pushSprite(int32_t x, int32_t y)
|
||||
{
|
||||
@@ -4312,6 +4441,23 @@ void TFT_eSprite::pushSprite(int32_t x, int32_t y)
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** 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 (_bpp16) _tft->pushSprite(x, y, _iwidth, _iheight, _img, transp );
|
||||
else
|
||||
{
|
||||
transp = (uint8_t)((transp & 0xE000)>>8 | (transp & 0x0700)>>6 | (transp & 0x0018)>>3);
|
||||
_tft->pushSprite(x, y, _iwidth, _iheight, _img8, (uint8_t) transp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************************
|
||||
** Function name: readPixel
|
||||
** Description: Read 565 colour of a pixel at defined coordinates
|
||||
@@ -4493,7 +4639,11 @@ void TFT_eSprite::fillSprite(uint32_t color)
|
||||
// Use memset if possible as it is super fast
|
||||
if(( (uint8_t)color == (uint8_t)(color>>8) ) && _bpp16)
|
||||
memset(_img, (uint8_t)color, _iwidth * _iheight * 2);
|
||||
else if (!_bpp16) memset(_img8, (uint8_t)color, _iwidth * _iheight);
|
||||
else if (!_bpp16)
|
||||
{
|
||||
color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
|
||||
memset(_img8, (uint8_t)color, _iwidth * _iheight);
|
||||
}
|
||||
|
||||
else fillRect(0, 0, _iwidth, _iheight, color);
|
||||
}
|
||||
@@ -4770,7 +4920,8 @@ void TFT_eSprite::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color)
|
||||
if (_bpp16)
|
||||
{
|
||||
color = (color >> 8) | (color << 8);
|
||||
while (h--) _img[x + _iwidth * y++] = (uint16_t) color;
|
||||
int32_t yp = x + _iwidth * y;
|
||||
while (h--) {_img[yp] = (uint16_t) color; yp += _iwidth;}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4823,14 +4974,17 @@ void TFT_eSprite::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t
|
||||
if ((y + h) > _iheight) h = _iheight - y;
|
||||
if ((w < 1) || (h < 1)) return;
|
||||
|
||||
int32_t yp = _iwidth * y;
|
||||
|
||||
if (_bpp16)
|
||||
{
|
||||
color = (color >> 8) | (color << 8);
|
||||
|
||||
while (h--)
|
||||
{
|
||||
int32_t ix = x, iw = w;
|
||||
while (iw--) _img[_iwidth * y + ix++] = (uint16_t) color;
|
||||
y++;
|
||||
uint32_t ix = x, iw = w;
|
||||
while (iw--) _img[yp + ix++] = (uint16_t) color;
|
||||
yp += _iwidth;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -4838,10 +4992,8 @@ void TFT_eSprite::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t
|
||||
color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
|
||||
while (h--)
|
||||
{
|
||||
//int32_t ix = x, iw = w;
|
||||
//while (iw--) _img8[_iwidth * y + ix++] = (uint8_t) color;
|
||||
memset(_img8 + _iwidth * y + x, (uint8_t)color, w);
|
||||
y++;
|
||||
memset(_img8 + yp + x, (uint8_t)color, w);
|
||||
yp += _iwidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
18
TFT_eSPI.h
18
TFT_eSPI.h
@@ -246,10 +246,14 @@
|
||||
#define TFT_MAGENTA 0xF81F /* 255, 0, 255 */
|
||||
#define TFT_YELLOW 0xFFE0 /* 255, 255, 0 */
|
||||
#define TFT_WHITE 0xFFFF /* 255, 255, 255 */
|
||||
#define TFT_ORANGE 0xFD20 /* 255, 165, 0 */
|
||||
#define TFT_GREENYELLOW 0xAFE5 /* 173, 255, 47 */
|
||||
#define TFT_PINK 0xF81F
|
||||
#define TFT_ORANGE 0xFDA0 /* 255, 180, 0 */
|
||||
#define TFT_GREENYELLOW 0xB7E0 /* 180, 255, 0 */
|
||||
#define TFT_PINK 0xFC9F
|
||||
|
||||
// Next is a special 16 bit colour value that encodes to 8 bits
|
||||
// and will then decode back to the same 16 bit value.
|
||||
// Convenient for 8 bit and 16 bit transparent sprites.
|
||||
#define TFT_TRANSPARENT 0x0120
|
||||
|
||||
// Swap any type
|
||||
template <typename T> static inline void
|
||||
@@ -406,6 +410,9 @@ class TFT_eSPI : public Print {
|
||||
void pushRect(uint32_t x0, uint32_t y0, uint32_t w, uint32_t h, uint16_t *data);
|
||||
void pushSprite(int32_t x0, int32_t y0, uint32_t w, uint32_t h, uint16_t *data);
|
||||
void pushSprite(int32_t x0, int32_t y0, uint32_t w, uint32_t h, uint8_t *data);
|
||||
void pushSprite(int32_t x0, int32_t y0, uint32_t w, uint32_t h, uint16_t *data, uint16_t transparent);
|
||||
void pushSprite(int32_t x0, int32_t y0, uint32_t w, uint32_t h, uint8_t *data, uint8_t transparent);
|
||||
|
||||
// This next function has been used successfully to dump the TFT screen to a PC for documentation purposes
|
||||
// It reads a screen area and returns the RGB 8 bit colour values of each pixel
|
||||
// Set w and h to 1 to read 1 pixel's colour. The data buffer must be at least w * h * 3 bytes
|
||||
@@ -590,6 +597,7 @@ class TFT_eSprite : public TFT_eSPI {
|
||||
void pushBitmap(uint32_t x0, uint32_t y0, uint32_t w, uint32_t h, uint16_t *data);
|
||||
|
||||
void pushSprite(int32_t x, int32_t y);
|
||||
void pushSprite(int32_t x, int32_t y, uint16_t transparent);
|
||||
|
||||
int16_t drawChar(unsigned int uniCode, int x, int y, int font),
|
||||
drawChar(unsigned int uniCode, int x, int y);
|
||||
@@ -607,11 +615,11 @@ class TFT_eSprite : public TFT_eSPI {
|
||||
|
||||
uint16_t *_img;
|
||||
uint8_t *_img8;
|
||||
bool _created;
|
||||
bool _created, _bpp16;
|
||||
|
||||
int32_t _icursor_x, _icursor_y, _xs, _ys, _xe, _ye, _xptr, _yptr;
|
||||
|
||||
int32_t _iwidth, _iheight, _bpp16;
|
||||
int32_t _iwidth, _iheight;
|
||||
|
||||
};
|
||||
|
||||
|
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
Sketch to show creation of a sprite with a transparent
|
||||
background, then plot it on the TFT.
|
||||
|
||||
Example for library:
|
||||
https://github.com/Bodmer/TFT_eSPI
|
||||
|
||||
A Sprite is notionally an invisible graphics screen that is
|
||||
kept in the processors RAM. Graphics can be drawn into the
|
||||
Sprite just as it can be drawn directly to the screen. Once
|
||||
the Sprite is completed it can be plotted onto the screen in
|
||||
any position. If there is sufficient RAM then the Sprite can
|
||||
be the same size as the screen and used as a frame buffer.
|
||||
|
||||
A 16 bit Sprite occupies (2 * width * height) bytes in RAM.
|
||||
|
||||
On a ESP8266 Sprite sizes up to 126 x 160 can be accomodated,
|
||||
this size requires 40kBytes of RAM for a 16 bit colour depth.
|
||||
|
||||
When 8 bit colour depth sprites are created they occupy
|
||||
(width * height) bytes in RAM, so larger sprites can be
|
||||
created, or the RAM required is halved.
|
||||
*/
|
||||
|
||||
#include <TFT_eSPI.h> // Include the graphics library (this includes the sprite functions)
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI(); // Create object "tft"
|
||||
|
||||
TFT_eSprite img = TFT_eSprite(&tft); // Create Sprite object "img" with pointer to "tft" object
|
||||
// the pointer is used by pushSprite() to push it onto the TFT
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(250000);
|
||||
|
||||
tft.init();
|
||||
|
||||
tft.setRotation(0);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
tft.fillScreen(TFT_NAVY);
|
||||
|
||||
// Draw 10 sprites containing a "transparent" colour
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
int x = random(240-70);
|
||||
int y = random(320-80);
|
||||
int c = random(0x10000); // Random colour
|
||||
drawStar(x, y, c);
|
||||
}
|
||||
|
||||
delay(2000);
|
||||
|
||||
uint32_t dt = millis();
|
||||
|
||||
// Now go bananas and draw 500 nore
|
||||
for (int i = 0; i < 500; i++)
|
||||
{
|
||||
int x = random(240-70);
|
||||
int y = random(320-80);
|
||||
int c = random(0x10000); // Random colour
|
||||
drawStar(x, y, c);
|
||||
yield(); // Stop watchdog reset
|
||||
}
|
||||
|
||||
// Show time in milliseconds to draw and then push 1 sprite to TFT screen
|
||||
numberBox( 10, 10, (millis()-dt)/500.0 );
|
||||
|
||||
delay(2000);
|
||||
|
||||
}
|
||||
|
||||
// #########################################################################
|
||||
// Create sprite, plot graphics in it, plot to screen, then delete sprite
|
||||
// #########################################################################
|
||||
void drawStar(int x, int y, int star_color)
|
||||
{
|
||||
// Create an 8 bit sprite 70x 80 pixels (uses 5600 bytes of RAM)
|
||||
img.setColorDepth(8);
|
||||
img.createSprite(70, 80);
|
||||
|
||||
// Fill Sprite with a "transparent" colour
|
||||
// TFT_TRANSPARENT is already defined for convenience
|
||||
// We could also fill with any colour as "transparent" and later specify that
|
||||
// same colour when we push the Sprite onto the screen.
|
||||
img.fillSprite(TFT_TRANSPARENT);
|
||||
|
||||
// Draw 2 triangles to create a filled in star
|
||||
img.fillTriangle(35, 0, 0,59, 69,59, star_color);
|
||||
img.fillTriangle(35,79, 0,20, 69,20, star_color);
|
||||
|
||||
// Punch a star shaped hole in the middle with a smaller transparent star
|
||||
img.fillTriangle(35, 7, 6,56, 63,56, TFT_TRANSPARENT);
|
||||
img.fillTriangle(35,73, 6,24, 63,24, TFT_TRANSPARENT);
|
||||
|
||||
// Push sprite to TFT screen CGRAM at coordinate x,y (top left corner)
|
||||
// Specify what colour is to be treated as transparent.
|
||||
img.pushSprite(x, y, TFT_TRANSPARENT);
|
||||
|
||||
// Delete it to free memory
|
||||
img.deleteSprite();
|
||||
|
||||
}
|
||||
|
||||
// #########################################################################
|
||||
// Draw a number in a rounded rectangle with some transparent pixels
|
||||
// #########################################################################
|
||||
void numberBox(int x, int y, float num )
|
||||
{
|
||||
|
||||
// Size of sprite
|
||||
#define IWIDTH 80
|
||||
#define IHEIGHT 35
|
||||
|
||||
// Create a 8 bit sprite 80 pixels wide, 35 high (2800 bytes of RAM needed)
|
||||
img.setColorDepth(8);
|
||||
img.createSprite(IWIDTH, IHEIGHT);
|
||||
|
||||
// Fill it with black (this will be the transparent colour this time)
|
||||
img.fillSprite(TFT_BLACK);
|
||||
|
||||
// Draw a background for the numbers
|
||||
img.fillRoundRect( 0, 0, 80, 35, 15, TFT_RED);
|
||||
img.drawRoundRect( 0, 0, 80, 35, 15, TFT_WHITE);
|
||||
|
||||
// Set the font parameters
|
||||
img.setTextSize(1); // Font size scaling is x1
|
||||
img.setTextColor(TFT_WHITE); // White text, no background colour
|
||||
|
||||
// Set text coordinate datum to middle right
|
||||
img.setTextDatum(MR_DATUM);
|
||||
|
||||
// Draw the number to 3 decimal places at 70,20 in font 4
|
||||
img.drawFloat(num, 3, 70, 20, 4);
|
||||
|
||||
// Push sprite to TFT screen CGRAM at coordinate x,y (top left corner)
|
||||
// All black pixels will not be drawn hence will show as "transparent"
|
||||
img.pushSprite(x, y, TFT_BLACK);
|
||||
|
||||
// Delete sprite to free up the RAM
|
||||
img.deleteSprite();
|
||||
}
|
||||
|
Reference in New Issue
Block a user