Rotating an Image

Hi everyone,
I am trying make a rotating image on a 160x80 st7735 display. I have some code that displays the image and rotates it but it flickers, I need it to rotate smoothly, does anyone know of a way to make an image rotate smoothly without flickering. Also it would be ideal if it worked on an Arduino Uno r4 but a normal Uno could also work. I don t have a lot of experience with Arduino programing so any help would be appreciated.

Here is my current code:

#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>

#define TFT_CS        10
#define TFT_RST       9
#define TFT_DC        8

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

const static uint8_t PROGMEM rotatingImage[] =
{ 56, 43,
  B00000000, B00000000, B00000001, B11111111, B10000000, B00000000, B00000000, 
  B00000000, B00000000, B00001111, B11111111, B11110000, B00000000, B00000000, 
  B00000000, B00000000, B00011111, B11111111, B11111100, B00000000, B00000000, 
  B00000000, B00000000, B00111111, B11111111, B11111110, B00000000, B00000000, 
  B00000000, B00000000, B00111111, B11111111, B11111110, B00000000, B00000000, 
  B00000000, B00000000, B01111111, B11111111, B11111111, B00000000, B00000000, 
  B00000000, B00000000, B01111111, B11100111, B11111111, B00000000, B00000000, 
  B00000000, B00000000, B11111100, B00000000, B00111111, B00000000, B00000000, 
  B00000000, B00000000, B11111111, B00000000, B11111111, B10000000, B00000000, 
  B00000000, B00000001, B11111111, B11111111, B11111111, B11000000, B00000000, 
  B00000000, B00000011, B11111111, B11111111, B11111111, B11000000, B00000000, 
  B00000000, B00000111, B11110000, B00000000, B00001111, B11100000, B00000000, 
  B00000000, B00001111, B10000000, B00000000, B00000001, B11110000, B00000000, 
  B00000000, B00011110, B00000000, B00000000, B00000000, B01110000, B00000000, 
  B00000000, B01111100, B00000000, B00000000, B00000000, B00111110, B00000000, 
  B00000001, B11111000, B00000000, B00000000, B00000000, B00011111, B10000000, 
  B00000111, B11111000, B00000000, B00000000, B00000000, B00011111, B11100000, 
  B00011111, B11110000, B00000000, B00000000, B00000000, B00001111, B11111000, 
  B00111110, B00110000, B00000000, B00000000, B00000000, B00001100, B00111110, 
  B11100000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000001, 
  B00000000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00011000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00011000, B00000000, B00000000, B00000000, B00011000, B00000000, 
  B00000000, B00011000, B00000000, B00000000, B00000000, B00011000, B00000000, 
  B00000000, B00001100, B00000000, B00000000, B00000000, B00011000, B00000000, 
  B00000000, B00001100, B00000000, B00000000, B00000000, B00110000, B00000000, 
  B00000000, B00000110, B00000000, B00000000, B00000000, B00110000, B00000000, 
  B00000000, B00000111, B00000000, B00000000, B00000000, B01100000, B00000000, 
  B00000000, B00000011, B00000000, B00000000, B00000000, B01100000, B00000000, 
  B00000000, B00000001, B10000000, B00000000, B00000000, B11000000, B00000000, 
  B00000000, B00000000, B11000000, B00000000, B00000001, B10000000, B00000000, 
  B00000000, B00000000, B00100000, B00000000, B00000000, B00000000, B00000000, 
  B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, 
  B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, 
  B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, 
  B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, 
  B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, 
  B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, 
  B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, B11111111

};

void setup() {
  Serial.begin(57600);

 tft.initR(INITR_MINI160x80); // Init ST7735S mini display
  // by default, we'll generate the high voltage from the 3.3v line
  tft.setRotation(3);  // Adjust the rotation based on your display orientation
}

void loop() {
  for (int angle = 0; angle < 360; angle += 6) {
    tft.fillScreen(ST7735_BLACK); // Clear the display

    drawRotatedBitmap(80, 40, rotatingImage, angle);

    delay(10); // Pause so we see it
  }
}


void drawRotatedBitmap(int16_t x, int16_t y, const uint8_t *bitmap, uint16_t angle) {

  uint8_t w = pgm_read_byte(bitmap++);
  uint8_t h = pgm_read_byte(bitmap++);

  int16_t rotatedx, rotatedy;
  uint8_t data = 0;

  float  cosa = cos(angle * 0.0174532925), sina = sin(angle * 0.0174532925);

  x = x - ((w * cosa / 2) - (h * sina / 2));
  y = y - ((h * cosa / 2) + (w * sina / 2));

  for (int16_t j = 0; j < h; j++) {
    for (int16_t i = 0; i < w; i++ ) {
      if ((j * w + i) & 7) data <<= 1;
      else      data   = pgm_read_byte(bitmap++);

      rotatedx = 0.5 + x + ((i * cosa) - (j * sina));
      rotatedy = 0.5 + y + ((j * cosa) + (i * sina));

      if (data & 0x80) tft.drawPixel(rotatedx, rotatedy, ST7735_WHITE);
      //else            display.drawPixel(newx, newy, 0);
    }
  }
}

Are you using an Uno R3 or similar?

If so, it is far too slow to do all that floating point math, with that many coordinates, and write to the display without flicker.

Rotating bitmaps over small angles is a separate complication that leads to flicker, because the display accepts only integer pixel coordinates. So the image will be unsteady as pixels with non-integer coordinates "jump" to neighboring locations.

You are probably working directly on the memory of the display.
When clearing the memory, it is directly shown on the display.

To avoid flickering, you have to prepare the image in RAM first and than copy it over to the display. Do not clear the display.

When you are lucky, you can activate double buffering in the library.

I removed the clear display from the loop and its much smoother but I need a way to clear the old pixels. I tried drawing a rectangle over the area but that still flickers. I also tried telling it to draw the black pixels but the rotation makes it skip some pixels and causes random white pixels to appear around the image.

You are writing twice to the display memory and it is happy to display both writes.

constexpr auto DISP_WIDTH = 160;
constexpr auto DISP_HEIGHT = 80;

uint16_t DISP_DATA[DISP_WIDTH*DISP_HEIGHT];

void setPixel(uint8_t x, uint8_t y, uint16_t c){
  if(x >= DISP_WIDTH || y >= DISP_HEIGHT)
    return;
  
  uint16_t addr = x + y * DISP_WIDTH;
  DISP_DATA[addr] = c;
}

uint16_t getPixel(uint8_t x, uint8_t y){
  if(x >= DISP_WIDTH || y >= DISP_HEIGHT)
    return 0;

  uint16_t addr = x + y * DISP_WIDTH;
  return DISP_DATA[addr];
}

void updateDisplay(){
  for(int y = 0; y < DISP_HEIGHT; y++){
    for(int x = 0; x < DISP_WIDTH; x++){
      auto c = getPixel(x, y);
      tft.drawPixel(x, y, c);
    }
  }
}

void fillPixel(uint16_t c=0x0000){
  for(uint16_t t = 0; t < (DISP_WIDTH * DISP_HEIGHT); t++){
    DISP_DATA[t] = c;
  }
}

Now your drawRotatedBitmap() first needs to call fillPixel().
Use setPixel() instead of tft.drawPixel() and when you are done rotating your image call updateDisplay().

I think double buffering uses to much memory although my code might not be optimal. I was able to make it a little faster but it still flickers a little.
here is my current code:

#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>

#define TFT_CS        10
#define TFT_RST       9
#define TFT_DC        8

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

const static uint8_t PROGMEM rotatingImage[] =
{ 56, 43,
  B00000000, B00000000, B00000001, B11111111, B10000000, B00000000, B00000000, 
  B00000000, B00000000, B00001111, B11111111, B11110000, B00000000, B00000000, 
  B00000000, B00000000, B00011111, B11111111, B11111100, B00000000, B00000000, 
  B00000000, B00000000, B00111111, B11111111, B11111110, B00000000, B00000000, 
  B00000000, B00000000, B00111111, B11111111, B11111110, B00000000, B00000000, 
  B00000000, B00000000, B01111111, B11111111, B11111111, B00000000, B00000000, 
  B00000000, B00000000, B01111111, B11100111, B11111111, B00000000, B00000000, 
  B00000000, B00000000, B11111100, B00000000, B00111111, B00000000, B00000000, 
  B00000000, B00000000, B11111111, B00000000, B11111111, B10000000, B00000000, 
  B00000000, B00000001, B11111111, B11111111, B11111111, B11000000, B00000000, 
  B00000000, B00000011, B11111111, B11111111, B11111111, B11000000, B00000000, 
  B00000000, B00000111, B11110000, B00000000, B00001111, B11100000, B00000000, 
  B00000000, B00001111, B10000000, B00000000, B00000001, B11110000, B00000000, 
  B00000000, B00011110, B00000000, B00000000, B00000000, B01110000, B00000000, 
  B00000000, B01111100, B00000000, B00000000, B00000000, B00111110, B00000000, 
  B00000001, B11111000, B00000000, B00000000, B00000000, B00011111, B10000000, 
  B00000111, B11111000, B00000000, B00000000, B00000000, B00011111, B11100000, 
  B00011111, B11110000, B00000000, B00000000, B00000000, B00001111, B11111000, 
  B00111110, B00110000, B00000000, B00000000, B00000000, B00001100, B00111110, 
  B11100000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000001, 
  B00000000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00110000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00011000, B00000000, B00000000, B00000000, B00001100, B00000000, 
  B00000000, B00011000, B00000000, B00000000, B00000000, B00011000, B00000000, 
  B00000000, B00011000, B00000000, B00000000, B00000000, B00011000, B00000000, 
  B00000000, B00001100, B00000000, B00000000, B00000000, B00011000, B00000000, 
  B00000000, B00001100, B00000000, B00000000, B00000000, B00110000, B00000000, 
  B00000000, B00000110, B00000000, B00000000, B00000000, B00110000, B00000000, 
  B00000000, B00000111, B00000000, B00000000, B00000000, B01100000, B00000000, 
  B00000000, B00000011, B00000000, B00000000, B00000000, B01100000, B00000000, 
  B00000000, B00000001, B10000000, B00000000, B00000000, B11000000, B00000000, 
  B00000000, B00000000, B11000000, B00000000, B00000001, B10000000, B00000000, 
  B00000000, B00000000, B00100000, B00000000, B00000000, B00000000, B00000000, 
  B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, 
  B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, 
  B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, 
  B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, 
  B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, 
  B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, 
  B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, B11111111


};

void setup() {
  Serial.begin(57600);

 tft.initR(INITR_MINI160x80); // Init ST7735S mini display
  // by default, we'll generate the high voltage from the 3.3v line
  tft.setRotation(3);  // Adjust the rotation based on your display orientation
  tft.fillScreen(ST7735_BLACK);
}

void loop() {
  for (int angle = 0; angle < 360; angle += 6) {
    
  tft.fillRect(40, 0, 80, 80, ST7735_BLACK); // Clear the display

    drawRotatedBitmap(80, 40, rotatingImage, angle);

    delay(10); // Pause so we see it
  }
}


void drawRotatedBitmap(int16_t x, int16_t y, const uint8_t *bitmap, uint16_t angle) {

  uint8_t w = pgm_read_byte(bitmap++);
  uint8_t h = pgm_read_byte(bitmap++);

  float cosa = cos(angle * 0.0174532925);
  float sina = sin(angle * 0.0174532925);

  // Calculate the center of rotation
  float centerx = x + w / 2.0;
  float centery = y + h / 2.0;

  // Adjust the position based on your desired center
  float targetx = tft.width() / 2.0;  // Set to half of the screen width
  float targety = tft.height() / 2.0; // Set to half of the screen height

  for (int16_t j = 0; j < h; j++) {
    for (int16_t i = 0; i < w; i++) {
      uint8_t data = pgm_read_byte(bitmap + (j * w + i) / 8);
      if (data & (1 << (7 - (i + j * w) % 8))) {
        // Calculate rotated coordinates around the adjusted center
        float rotatedx = targetx + (i - w / 2.0) * cosa - (j - h / 2.0) * sina;
        float rotatedy = targety + (i - w / 2.0) * sina + (j - h / 2.0) * cosa;
        tft.drawPixel(rotatedx, rotatedy, ST7735_WHITE);
      }
    }
  }
}

As long as you rely on fillScreen or fillRect before copying the image to the display it will always flicker.

How about the other way round?
Instead of going over the source image and calculate where to place this pixel you go over the target area and calculate which pixel you need from the source image?

void drawImage(){
  for( ty=0; ty< height; ty++){
    for( tx=0; tx<width; tx++){
      sx,sy = calcSourcePosition(tx, ty, angle);
      c = getSourcePixel(sx, sy);
      tft.setPixel(tx,ty, c);
    }
  }
}

If you are using TFT_eSPI library use sprite for such use cases.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.