Animation colors distorted with 90° rotation and LV_COLOR_16_SWAP=0 setup on ESP32 + .95 inch AMOLED

Hi LVGL community,

I’m working on an ESP32 project using LVGL 8.3.11 and a 0.95" Amoled SH8501A display (240x120). I have set up my display with a 90° clockwise rotation manually inside the my_disp_flush function. Static screens and simple color fills work perfectly, but when I run animations (e.g., buttons, progress bars), the colors become distorted and flicker badly with mixed orange/purple/other colors.
display (1)

#include <lvgl.h>
#include “lcd.h”
#include “lcd_init.h”
#include “spi_amoled.h”
#include “lcdfont.h”
#include “lcd_draw_clock.h”
#include <SPI.h>
#include “lvgl_font.c”
#include “ui.h”

// Physical LCD dimensions
static const uint16_t LCD_WIDTH = 120;
static const uint16_t LCD_HEIGHT = 240;
static const uint16_t screenWidth = 240;
static const uint16_t screenHeight = 120;

// Buffer configuration
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf1[screenWidth * screenHeight / 2];

volatile uint32_t flush_call_count = 0;

#if LV_USE_LOG != 0
void my_print(const char * buf) {
Serial.printf(buf);
Serial.flush();
}
#endif

void my_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;

// REMOVE SMALL UPDATE SKIPPING - handle all updates properly
// Animation needs all updates, even small ones

// MANUAL ROTATION: 90-degree clockwise transformation
const uint16_t SCREEN_W_MINUS_1 = screenWidth - 1;

// Calculate physical coordinates for 90-degree clockwise rotation
uint16_t x1_phys = area->y1;
uint16_t y1_phys = SCREEN_W_MINUS_1 - area->x2;
uint16_t x2_phys = area->y2;
uint16_t y2_phys = SCREEN_W_MINUS_1 - area->x1;

// Clamp to LCD bounds
x1_phys = (x1_phys < LCD_WIDTH) ? x1_phys : (LCD_WIDTH - 1);
x2_phys = (x2_phys < LCD_WIDTH) ? x2_phys : (LCD_WIDTH - 1);
y1_phys = (y1_phys < LCD_HEIGHT) ? y1_phys : (LCD_HEIGHT - 1);
y2_phys = (y2_phys < LCD_HEIGHT) ? y2_phys : (LCD_HEIGHT - 1);

// Validate coordinates
if (x1_phys > x2_phys || y1_phys > y2_phys) {
    lv_disp_flush_ready(disp_drv);
    return;
}

// Set LCD address window
LCD_Address_Set(x1_phys, y1_phys, x2_phys, y2_phys);

// Calculate physical dimensions
uint32_t phys_w = x2_phys - x1_phys + 1;
uint32_t phys_h = y2_phys - y1_phys + 1;
uint32_t total_pixels = phys_w * phys_h;

LCD_DC_Set();
uint16_t *src = (uint16_t*)color_p;

// VERY SMALL BATCHES for animation precision
static uint8_t pixel_buffer[2048];  // Even smaller buffer for better precision
uint32_t pixels_processed = 0;

while (pixels_processed < total_pixels) {
    uint32_t batch_size = std::min((uint32_t)1024, total_pixels - pixels_processed);  // Very small batches
    uint32_t buffer_index = 0;
    
    for (uint32_t i = 0; i < batch_size; i++) {
        uint32_t pixel_idx = pixels_processed + i;
        uint32_t px = pixel_idx % phys_w;
        uint32_t py = pixel_idx / phys_w;
        
        // Transform back to logical coordinates
        uint32_t logical_x = SCREEN_W_MINUS_1 - (y1_phys + py);
        uint32_t logical_y = x1_phys + px;
        
        uint16_t pixel_value = 0x0000;  // Default to black
        
        // STRICT bounds check and pixel lookup
        if (logical_x >= area->x1 && logical_x <= area->x2 && 
            logical_y >= area->y1 && logical_y <= area->y2) {
            
            uint32_t src_x = logical_x - area->x1;
            uint32_t src_y = logical_y - area->y1;
            
            // Additional safety check
            if (src_x < w && src_y < h) {
                uint32_t src_idx = src_y * w + src_x;
                
                if (src_idx < (w * h)) {
                    pixel_value = src[src_idx];
                }
            }
        }
        
        pixel_buffer[buffer_index++] = pixel_value >> 8;
        pixel_buffer[buffer_index++] = pixel_value & 0xFF;
    }
    
    LCD_WR_Bus_Multi(pixel_buffer, batch_size * 2);
    pixels_processed += batch_size;
}

lv_disp_flush_ready(disp_drv);

}

void my_touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) {
data->state = LV_INDEV_STATE_REL;
}

void LCD_GPIOInit(void) {
pinMode(CS_PIN, OUTPUT);
pinMode(DC_PIN, OUTPUT);
pinMode(RST_PIN, OUTPUT);
LCD_CS_Set();
LCD_DC_Set();
LCD_RES_Set();

// Back to normal SPI speed
SPI.begin();
SPI.setFrequency(SPI_FREQUENCY);
SPI.setDataMode(SPI_MODE);
SPI.setBitOrder(SPI_BIT_ORDER);

delay(10);

Serial.println("*** PIXEL FORMAT FIX VERSION ***");

}

void setup() {
Serial.begin(115200);
Serial.println(“=== PIXEL FORMAT FIX VERSION ===”);
Serial.println(“This should fix the color bleeding during animations”);

// Initialize LVGL
lv_init();

// Initialize LCD
LCD_GPIOInit();
LCD_Init();
LCD_Fill(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1, BLACK);
delay(1000);

#if LV_USE_LOG != 0
lv_log_register_print_cb(my_print);
#endif

// Initialize display buffer
lv_disp_draw_buf_init(&draw_buf, buf1, NULL, screenWidth * screenHeight / 2);

// Create display driver
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;

// Disable anti-aliasing to prevent color bleeding
#if LV_USE_ANTIALIAS
disp_drv.antialiasing = 0;
#endif

lv_disp_t * disp = lv_disp_drv_register(&disp_drv);

// Input device
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;
lv_indev_drv_register(&indev_drv);

// Initialize UI
ui_init();

Serial.println("=== TESTING INSTRUCTIONS ===");
Serial.println("1. Test your animation");
Serial.println("2. If bleeding still occurs, try different fix methods:");
Serial.println("   - Comment out fix_pixel_format()");
Serial.println("   - Uncomment fix_pixel_format_method2()");
Serial.println("   - If that doesn't work, try method3");
Serial.println("==============================");

}

void loop() {
static uint32_t last_tick = 0;
uint32_t now = millis();

if (now - last_tick >= 5) {
    lv_tick_inc(5);
    last_tick = now;
}

lv_timer_handler();
delay(1);

}

I also try with no rotation and minimal flush function the issue still exist.

Thank you for your assistance

The issue was the LVGL not clear the screen properly so i manually clear each widgets manually

lv_obj_invalidate((lv_obj_t *)obj); add in callback that help to fix the issue