Add Sprite rotation and examples

Sprites can be rendered rotated with defined pivot points, 3 new
examples have been added "Rotated_Sprite_1/2/3".
The FLASH bitmap example has been moved to the Generic examples folder.
Boundary checks have been added to pushImage()
A new scrolling and wrapping in a Sprite example has been added.
This commit is contained in:
Bodmer committed 2019-01-06 01:14:10 +00:00
1 parent e8c201db5e
commit 96228cbf44
30 files changed
+1164 -656

No files matched your search

@@ -0,0 +1,182 @@
// This example plots a rotated Sprite to the screen using the pushRotated()
// function. It is written for a 240 x 320 TFT screen.
// Two rotation pivot points must be set, one for the Sprite and one for the TFT
// using setPivot(). These pivot points do not need to be within the visible screen
// or Sprite boundary.
// When the Sprite is rotated and pushed to the TFT with pushRotated(angle) it will be
// drawn so that the two pivot points coincide. This makes rotation about a point on the
// screen very simple. The rotation is clockwise with increasing angle. The angle is in
// degrees, an angle of 0 means no Sprite rotation.
// The pushRotated() function works with 1, 8 and 16 bit per pixel (bpp) Sprites.
// The original Sprite is unchanged so can be plotted again at a different angle.
// Optionally a transparent colour can be defined, pixels of this colour will
// not be plotted to the TFT.
// For 1 bpp Sprites the foreground and background colours are defined with the
// function spr.setBitmapColor(foregroundColor, backgroundColor).
// Created by Bodmer 6/1/19 as an example to the TFT_eSPI library:
// https://github.com/Bodmer/TFT_eSPI
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI(); // TFT object
TFT_eSprite spr = TFT_eSprite(&tft); // Sprite object
// =======================================================================================
// Setup
// =======================================================================================
void setup() {
Serial.begin(250000); // Debug only
tft.begin(); // initialize
tft.setRotation(0);
}
// =======================================================================================
// Loop
// =======================================================================================
void loop() {
int xw = tft.width()/2; // xw, yh is midle of screen
int yh = tft.height()/2;
showMessage("90 degree angles");
tft.setPivot(xw, yh); // Set pivot to middle of TFT screen
drawX(xw, yh); // Show where screen pivot is
// Create the Sprite
spr.setColorDepth(8); // Create an 8bpp Sprite of 60x30 pixels
spr.createSprite(64, 30); // 8bpp requires 64 * 30 = 1920 bytes
spr.setPivot(32, 55); // Set pivot relative to top left corner of Sprite
spr.fillSprite(TFT_BLACK); // Fill the Sprite with black
spr.setTextColor(TFT_GREEN); // Green text
spr.setTextDatum(MC_DATUM); // Middle centre datum
spr.drawString("Hello", 32, 15, 4); // Plot text, font 4, in Sprite at 30, 15
spr.pushRotated(0);
spr.pushRotated(90);
spr.pushRotated(180);
spr.pushRotated(270);
delay(2000);
showMessage("45 degree angles");
drawX(xw, yh); // Show where screen pivot is
spr.pushRotated(45);
spr.pushRotated(135);
spr.pushRotated(225);
spr.pushRotated(315);
delay(2000); // Pause so we see it
showMessage("Moved Sprite pivot point");
drawX(xw, yh); // Show where screen pivot is
spr.setPivot(-20, 15); // Change just the Sprite pivot point
spr.pushRotated(45);
spr.pushRotated(135);
spr.pushRotated(225);
spr.pushRotated(315);
delay(2000); // Pause so we see it
showMessage("Moved TFT pivot point");
tft.setPivot(100, 100); // Change just the TFT pivot point
drawX(100, 100); // Show where pivot is
spr.pushRotated(45);
spr.pushRotated(135);
spr.pushRotated(225);
spr.pushRotated(315);
delay(2000); // Pause so we see it
showMessage("Transparent rotations");
tft.fillCircle(xw, yh, 70, TFT_DARKGREY); // Draw a filled circle
tft.setPivot(xw, yh); // Set pivot to middle of screen
drawX(xw, yh); // Show where pivot is
spr.deleteSprite();
spr.setColorDepth(8); // Create a 8bpp Sprite
spr.createSprite(40, 30); // Create a new Sprite 40x30
spr.setPivot(20, 70); // Set Sprite pivot at 20,80
spr.setTextColor(TFT_RED); // Red text in Sprite
spr.setTextDatum(MC_DATUM); // Middle centre datum
int num = 1;
for (int16_t angle = 30; angle <= 360; angle += 30)
{
spr.fillSprite(TFT_BLACK); // Clear the Sprite
spr.drawNumber(num, 20, 15, 4); // Plot number, in Sprite at 15,15 and with font 4
spr.pushRotated(angle, TFT_BLACK); // Plot rotated Sprite, black being transparent
num++;
}
spr.setTextColor(TFT_WHITE); // White text in Sprite
spr.setPivot(-75, 15); // Set Sprite pivot at -75,15
for (int16_t angle = -90; angle < 270; angle += 30)
{
spr.fillSprite(TFT_BLACK); // Clear the Sprite
spr.drawNumber(angle+90, 15, 15, 4); // Plot number, in Sprite at 15,15 and with font 4
spr.pushRotated(angle, TFT_BLACK); // Plot rotated Sprite, black being transparent
num++;
}
delay(8000); // Pause so we see it
spr.deleteSprite();
}
// =======================================================================================
// Draw an X centered on x,y
// =======================================================================================
void drawX(int x, int y)
{
tft.drawLine(x-5, y-5, x+5, y+5, TFT_WHITE);
tft.drawLine(x-5, y+5, x+5, y-5, TFT_WHITE);
}
// =======================================================================================
// Show a message at the top of the screen
// =======================================================================================
void showMessage(String msg)
{
// Clear the screen areas
tft.fillRect(0, 0, tft.width(), 20, TFT_BLACK);
tft.fillRect(0, 20, tft.width(), tft.height()-20, TFT_BLUE);
uint8_t td = tft.getTextDatum(); // Get current datum
tft.setTextDatum(TC_DATUM); // Set new datum
tft.drawString(msg, tft.width()/2, 2, 2); // Message in font 2
tft.setTextDatum(td); // Restore old datum
}
// =======================================================================================
@@ -0,0 +1,181 @@
// This example plots a rotated Sprite into another Sprite and then the resultant composited
// Sprite is pushed to the TFT screen. This example is for a 240 x 320 screen.
// The motivation for developing this capability is that animated dials can be drawn easily
// and the complex calculations involved are handled by the TFT_eSPI library. To create a dial
// with a moving needle a graphic of a meter needle is plotted at a specified angle into another
// Sprite that contains the dial face. When the needle Sprite is pushed to the dial Sprite the
// plotting ensures two pivot points for each Sprite coincide with pixel level accuracy.
// Two rotation pivot points must be set, one for the first Sprite and one for the second
// Sprite using setPivot(). These pivot points do not need to be within the Sprite boundaries.
// In this example a needle graphic is also be plotted direct to a defined TFT pivot point.
// The rotation angle is in degrees, an angle of 0 means no Sprite rotation.
// The pushRotated() function works with 1, 8 and 16 bit per pixel (bpp) Sprites.
// For 1 bpp Sprites the foreground and background colours are defined with the
// member function setBitmapColor(foregroundColor, backgroundColor).
// Created by Bodmer 6/1/19 as an example to the TFT_eSPI library:
// https://github.com/Bodmer/TFT_eSPI
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite dial = TFT_eSprite(&tft); // Sprite object for dial
TFT_eSprite needle = TFT_eSprite(&tft); // Sprite object for needle
uint32_t startMillis;
int16_t angle = 0;
// =======================================================================================
// Setup
// =======================================================================================
void setup() {
Serial.begin(250000); // Debug only
tft.begin();
tft.setRotation(1);
// Clear TFT screen
tft.fillScreen(TFT_NAVY);
// Create the dial Sprite and dial (this temporarily hijacks the use of the needle Sprite)
createDialScale(-120, 120, 30); // create scale (start angle, end angle, increment angle)
drawEmptyDial("Label", 12345); // draw the centre of dial in the Sprite
dial.pushSprite(110, 0); // push a copy of the dial to the screen so we can see it
// Create the needle Sprite
createNeedle(); // draw the needle graphic
needle.pushSprite(95, 7); // push a copy of the needle to the screen so we can see it
}
// =======================================================================================
// Loop
// =======================================================================================
void loop() {
// Push the needle sprite to the dial Sprite at different angles and then push the dial to the screen
// Use angle increments in range 1 to 6 for smoother or faster movement
for (int16_t angle = -120; angle <= 120; angle += 2) {
plotDial(0, 0, angle, "RPM", angle + 120);
delay(25);
yield(); // Avoid a watchdog time-out
}
delay(1000); // Pause
// Update the dial Sprite with decreasing angle and plot to screen at 0,0, no delay
for (int16_t angle = 120; angle >= -120; angle -= 2) {
plotDial(0, 0, angle, "RPM", angle + 120);
yield(); // Avoid a watchdog time-out
}
// Now show plotting of the rotated needle direct to the TFT
tft.setPivot(45, 150); // Set the TFT pivot point that the needle will rotate around
// The needle graphic has a black border so if the angle increment is small
// (6 degrees or less in this case) it wipes the last needle position when
// it is rotated and hence it clears the swept area to black
for (int16_t angle = 0; angle <= 360; angle += 5)
{
needle.pushRotated(angle); // Plot direct to TFT at specifed angle
yield(); // Avoid a watchdog time-out
}
}
// =======================================================================================
// Create the dial sprite, the dial outer and place scale markers
// =======================================================================================
void createDialScale(int16_t start_angle, int16_t end_angle, int16_t increment)
{
// Create the dial Sprite
dial.setColorDepth(8); // Size is odd (i.e. 91) so there is a centre pixel at 45,45
dial.createSprite(91, 91); // 8bpp requires 91 * 91 = 8281 bytes
dial.setPivot(45, 45); // set pivot in middle of dial Sprite
// Draw dial outline
dial.fillSprite(TFT_TRANSPARENT); // Fill with transparent colour
dial.fillCircle(45, 45, 43, TFT_DARKGREY); // Draw dial outer
// Hijack the use of the needle Sprite since that has not been used yet!
needle.createSprite(3, 3); // 3 pixels wide, 3 high
needle.fillSprite(TFT_WHITE); // Fill with white
needle.setPivot(1, 43); // Set pivot point x to the Sprite centre and y to marker radius
for (int16_t angle = start_angle; angle <= end_angle; angle += increment) {
needle.pushRotated(&dial, angle); // Sprite is used to make scale markers
yield(); // Avoid a watchdog time-out
}
needle.deleteSprite(); // Delete the hijacked Sprite
}
// =======================================================================================
// Add the empty dial face with label and value
// =======================================================================================
void drawEmptyDial(String label, int16_t val)
{
// Draw black face
dial.fillCircle(45, 45, 40, TFT_BLACK);
dial.drawPixel(45, 45, TFT_WHITE); // For demo only, mark pivot point with a while pixel
dial.setTextDatum(TC_DATUM); // Draw dial text
dial.drawString(label, 45, 15, 2);
dial.drawNumber(val, 45, 60, 2);
}
// =======================================================================================
// Update the dial and plot to screen with needle at defined angle
// =======================================================================================
void plotDial(int16_t x, int16_t y, int16_t angle, String label, uint16_t val)
{
// Draw the blank dial in the Sprite, add label and number
drawEmptyDial(label, val);
// Push a rotated needle Sprite to the dial Sprite, with black as transparent colour
needle.pushRotated(&dial, angle, TFT_BLACK); // dial is the destination Sprite
// Push the resultant dial Sprite to the screen, with transparent colour
dial.pushSprite(x, y, TFT_TRANSPARENT);
}
// =======================================================================================
// Create the needle Sprite and the image of the needle
// =======================================================================================
void createNeedle(void)
{
needle.setColorDepth(8);
needle.createSprite(11, 49); // create the needle Sprite 11 pixels wide by 49 high
needle.fillSprite(TFT_BLACK); // Fill with black
// Define needle pivot point
uint16_t piv_x = needle.width() / 2; // x pivot of Sprite (middle)
uint16_t piv_y = needle.height() - 10; // y pivot of Sprite (10 pixels from bottom)
needle.setPivot(piv_x, piv_y); // Set pivot point in this Sprite
// Draw the red needle with a yellow tip
// Keep needle tip 1 pixel inside dial circle to avoid leaving stray pixels
needle.fillRect(piv_x - 1, 2, 3, piv_y + 8, TFT_RED);
needle.fillRect(piv_x - 1, 2, 3, 5, TFT_YELLOW);
// Draw needle centre boss
needle.fillCircle(piv_x, piv_y, 5, TFT_MAROON);
needle.drawPixel( piv_x, piv_y, TFT_WHITE); // Mark needle pivot point with a white pixel
}
// =======================================================================================
@@ -0,0 +1,140 @@
/*====================================================================================
This example draws a jpeg image in a Sprite then plots a rotated copy of the Sprite
to the TFT.
The jpeg used is in the sketch Data folder (presss Ctrl+K to see folder)
The jpeg must be uploaded to the ESP8266 or ESP32 SPIFFS by using the Tools menu
sketch data upload option of the Arduino IDE. If you do not have that option it can
be added. Close the Serial Monitor window before uploading to avoid an error message!
To add the upload option for the ESP8266 see:
http://www.esp8266.com/viewtopic.php?f=32&t=10081
https://github.com/esp8266/arduino-esp8266fs-plugin/releases
To add the upload option for the ESP32 see:
https://github.com/me-no-dev/arduino-esp32fs-plugin
Created by Bodmer 6/1/19 as an example to the TFT_eSPI library:
https://github.com/Bodmer/TFT_eSPI
Extension funtions in the TFT_eFEX library are used to list SPIFFS files and render
the jpeg to the TFT and to the Sprite:
https://github.com/Bodmer/TFT_eFEX
To render the Jpeg image the JPEGDecoder library is needed, this can be obtained
with the IDE library manager, or downloaded from here:
https://github.com/Bodmer/JPEGDecoder
==================================================================================*/
//====================================================================================
// Libraries
//====================================================================================
// Call up the SPIFFS FLASH filing system, this is part of the ESP Core
#define FS_NO_GLOBALS
#include <FS.h>
#ifdef ESP32
#include "SPIFFS.h" // Needed for ESP32 only
#endif
// https://github.com/Bodmer/TFT_eSPI
#include <TFT_eSPI.h> // Hardware-specific library
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
TFT_eSprite spr = TFT_eSprite(&tft); // Create Sprite object "spr" with pointer to "tft" object
// https://github.com/Bodmer/TFT_eFEX
#include <TFT_eFEX.h> // Include the function extension library
TFT_eFEX fex = TFT_eFEX(&tft); // Create TFT_eFX object "fex" with pointer to "tft" object
//====================================================================================
// Setup
//====================================================================================
void setup()
{
Serial.begin(250000); // Used for messages
tft.begin();
tft.setRotation(0); // 0 & 2 Portrait. 1 & 3 landscape
tft.fillScreen(TFT_BLACK);
// Create a sprite to hold the jpeg (or part of it)
spr.createSprite(80, 64);
// Initialise SPIFFS
if (!SPIFFS.begin()) {
Serial.println("SPIFFS initialisation failed!");
while (1) yield(); // Stay here twiddling thumbs waiting
}
Serial.println("\r\nInitialisation done.\r\n");
// Lists the files so you can see what is in the SPIFFS
fex.listSPIFFS();
// Note the / before the SPIFFS file name must be present, this means the file is in
// the root directory of the SPIFFS, e.g. "/tiger.jpg" for a file called "tiger.jpg"
// Send jpeg info to serial port
fex.jpegInfo("/Eye_80x64.jpg");
// Draw jpeg iamge in Sprite spr at 0,0
fex.drawJpeg("/Eye_80x64.jpg", 0 , 0, &spr);
}
//====================================================================================
// Loop
//====================================================================================
void loop()
{
tft.fillScreen(random(0xFFFF));
// Set the TFT pivot point to the centre of the screen
tft.setPivot(tft.width() / 2, tft.height() / 2);
// Set Sprite pivot point to centre of Sprite
spr.setPivot(spr.width() / 2, spr.height() / 2);
// Push Sprite to the TFT at 0,0 (not rotated)
spr.pushSprite(0, 0);
delay(1000);
// Push copies of Sprite rotated through increasing angles 0-360 degrees
// with 45 fegree increments
for (int16_t angle = 0; angle <= 360; angle += 45) {
spr.pushRotated(angle);
delay(500);
}
delay(2000);
// Move Sprite pivot to a point above the image at 40,-60
// (Note: Top left corner is Sprite coordinate 0,0)
// The TFT pivot point has already been set to middle of screen.
/* .Pivot point at 40,-60
^
|
-60
< 40 >|
______V______
| |
| Sprite |
|_____________|
*/
spr.setPivot(40, -60);
// Push Sprite to screen rotated about the new pivot points
// negative angle rotates Sprite anticlockwise
for (int16_t angle = 330; angle >= 0; angle -= 30) {
spr.pushRotated(angle);
yield(); // Stop watchdog triggering
}
delay(5000);
}
//====================================================================================
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

@@ -0,0 +1,139 @@
// This **ONLY** works for 1 and 16 bpp Sprites due to lack of bounds checking in the
// Sprite pushImage() function for 8 bit Sprites (it is on the TO DO list)
// Wrapping scroll example by Bodmer for the TFT_eSPI library
//==========================================================================================
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite gfx = TFT_eSprite(&tft); // Sprite object for graphics write
TFT_eSprite fb = TFT_eSprite(&tft); // Sprite object for frame buffer
// Width and height of the Sprite
#define WIDTH 60
#define HEIGHT 60
// Define scroll increment in x and y directions
// positive numbers = right, down
// negative numbers = left, up
#define XDELTA 1
#define YDELTA 1
int16_t scroll_x = 0; // Keep track of the scrolled position, this is where the origin
int16_t scroll_y = 0; // (top left) of the gfx Sprite will be
int16_t radius = 5; // radius of circle
bool grow = true; // grow or shrink circle
uint16_t *gfxPtr; // Pointer to start of graphics sprite memory area
//==========================================================================================
//==========================================================================================
void setup() {
Serial.begin(115200);
tft.init();
tft.fillScreen(TFT_BLACK);
tft.setRotation(1);
//tft.invertDisplay(true);
// Create a 1bpp sprite for the graphics
gfx.setColorDepth(1);
gfxPtr = (uint16_t*) gfx.createSprite(WIDTH, HEIGHT); // 480 bytes needed
gfx.fillSprite(TFT_BLACK); // Note: Sprite is filled with black when created
// Create a 1bpp sprite for the frame buffer
fb.setColorDepth(1);
fb.createSprite(WIDTH, HEIGHT); // 480 bytes needed
fb.fillSprite(TFT_BLACK); // Note: Sprite is filled with black when created
// Text colour and alignment in graphics Sprite
gfx.setTextColor(TFT_WHITE, TFT_BLACK);
gfx.setTextDatum(MC_DATUM);
// Text colour and alignment in frame buffer
fb.setTextColor(TFT_WHITE, TFT_BLACK);
fb.setTextDatum(MC_DATUM);
// Next 3 lines are for test only to see what we have drawn
drawGraphics(); // draw the graphics in the gfx sprite and copy to buffer
gfx.pushSprite(0, 0); // Plot to screen so we see what it looks like
delay(2000);
}
//==========================================================================================
//==========================================================================================
void loop() {
uint32_t tnow = millis();
drawGraphics(); // Not needed if scrolling graphics are static
wrappingScroll(XDELTA, YDELTA);
// Plot two copies without "Hello", then one in the middle with "Hello"
//fb.setBitmapColor(TFT_WHITE, TFT_RED );
fb.pushSprite(0, 0); // Plot frame buffer onto the TFT screen
//fb.setBitmapColor(TFT_WHITE, TFT_BLUE );
fb.pushSprite(120, 0); // Plot frame buffer onto the TFT screen
fb.drawString("Hello", 30, 20, 2); // Plot hello in frame buffer
//fb.setBitmapColor(TFT_BLACK, TFT_GREEN);
fb.pushSprite(60, 0); // Plot frame buffer in middle of other two
//Serial.println(millis() - tnow);
delay(20);
}
//==========================================================================================
// Draw graphics in the master Sprite
//==========================================================================================
void drawGraphics(void)
{
gfx.fillSprite(TFT_BLACK); // Clear sprite each time and completely redraw
//gfx.drawRect(0,0,60,60,TFT_WHITE); // Test to check alignment
gfx.drawCircle( 30 , 30, radius, TFT_WHITE);
if (grow) radius++;
else radius--;
if ( radius > 25 ) grow = false;
if ( radius < 1 ) grow = true;
gfx.drawString("World", 30, 40, 2); // Plot hello in frame buffer
}
//==========================================================================================
// This function scrolls and wraps the graphic in a 1 bit per pixel Sprite, dy and dy
// control the scroll direction. Pixels that scroll off one side appear on the other.
// Scrolling is achieved by plotting one Sprite inside another with an offset. This has
// to be done by plotting 4 times into a second frame buffer Sprite.
//==========================================================================================
void wrappingScroll(int16_t dx, int16_t dy)
{
// Position the quadrants so they overlap all areas of the buffer
scroll_x += dx;
if (scroll_x < -WIDTH) scroll_x += WIDTH;
if (scroll_x > 0) scroll_x -= WIDTH;
scroll_y += dy;
if (scroll_y < -HEIGHT) scroll_y += HEIGHT;
if (scroll_y > 0) scroll_y -= HEIGHT;
// push the 4 quadrants of gfx. sprite into the fb. sprite
// pushImage will do the cropping
fb.pushImage(scroll_x, scroll_y, WIDTH, HEIGHT, gfxPtr);
fb.pushImage(scroll_x + WIDTH, scroll_y, WIDTH, HEIGHT, gfxPtr);
fb.pushImage(scroll_x, scroll_y + HEIGHT, WIDTH, HEIGHT, gfxPtr);
fb.pushImage(scroll_x + WIDTH, scroll_y + HEIGHT, WIDTH, HEIGHT, gfxPtr);
}
//==========================================================================================