BMP color does not match the original

The bmp colors don't match the original, what should be red, yellow and green in the bmp file is actually all blue, where is the error?

#include <TFT_eSPI.h>
#include <SPIFFS.h>

TFT_eSPI tft = TFT_eSPI();

const int FRAME_DELAY = 100;
const char* eyeFrames[] = {"/eyes.bmp", "/eyes2.bmp", "/eyes3.bmp", "/eyes4.bmp"};
const int TOTAL_FRAMES = 4;

uint16_t read16(File &f) {
    uint16_t result;
    ((uint8_t *)&result)[0] = f.read();
    ((uint8_t *)&result)[1] = f.read();
    return result;
}

uint32_t read32(File &f) {
    uint32_t result;
    ((uint8_t *)&result)[0] = f.read();
    ((uint8_t *)&result)[1] = f.read();
    ((uint8_t *)&result)[2] = f.read();
    ((uint8_t *)&result)[3] = f.read();
    return result;
}

void drawBmp(const char *filename, int16_t x, int16_t y, float scaleX = 1.0, float scaleY = 1.0) {
    if (!SPIFFS.exists(filename)) {
        Serial.println("File tidak ditemukan!");
        return;
    }
    
    File bmpFile = SPIFFS.open(filename, "r");
    if (!bmpFile) {
        Serial.println("Gagal membuka file!");
        return;
    }
    
    if (read16(bmpFile) != 0x4D42) {
        Serial.println("Bukan file BMP!");
        bmpFile.close();
        return;
    }
    
    read32(bmpFile); // File size
    read32(bmpFile); // Reserved
    uint32_t imageOffset = read32(bmpFile);
    read32(bmpFile); // Header size
    int32_t width = read32(bmpFile);
    int32_t height = read32(bmpFile);
    
    uint16_t planes = read16(bmpFile);
    uint16_t bitsPerPixel = read16(bmpFile);
    
    if (planes != 1 || bitsPerPixel != 24) {
        Serial.println("Format tidak didukung! Hanya 24-bit BMPs.");
        bmpFile.close();
        return;
    }
    
    read32(bmpFile); // Skip compression
    
    // Alokasi buffer untuk satu baris gambar
    uint8_t *lineBuffer = new uint8_t[width * 3];
    uint16_t *displayBuffer = new uint16_t[width];
    
    if (!lineBuffer || !displayBuffer) {
        Serial.println("Alokasi buffer gagal!");
        delete[] lineBuffer;
        delete[] displayBuffer;
        bmpFile.close();
        return;
    }
    
    int rowSize = (width * 3 + 3) & ~3;
    bool topDown = (height < 0);
    height = (height < 0) ? -height : height;
    
    for (int row = 0; row < height; row++) {
        int pos = topDown ? row : (height - 1 - row);
        bmpFile.seek(imageOffset + pos * rowSize);
        
        if (bmpFile.read(lineBuffer, width * 3) != width * 3) {
            Serial.println("Gagal membaca data gambar!");
            break;
        }
        
        // Konversi BGR ke RGB dan ke format color565 dengan benar
        for (int col = 0; col < width; col++) {
            uint8_t b = lineBuffer[col * 3];
            uint8_t g = lineBuffer[col * 3 + 1];
            uint8_t r = lineBuffer[col * 3 + 2];
            
            // Konversi ke format 565 dengan akurasi warna yang lebih baik
            displayBuffer[col] = ((b & 0xF8) >> 3) | 
                                 ((g & 0xFC) << 3) | 
                                 ((r & 0xF8) << 8);
        }
        
        // Gambar baris dengan pertimbangan skala
        int yPos = y + (row * scaleY);
        if (yPos < tft.height()) {
            tft.pushImage(x, yPos, width, 1, displayBuffer);
        }
    }
    
    delete[] lineBuffer;
    delete[] displayBuffer;
    bmpFile.close();
}

void setup() {
    Serial.begin(115200);
    if (!SPIFFS.begin(true)) {
        Serial.println("SPIFFS mount gagal!");
        return;
    }
    tft.init();
    tft.setRotation(1);
    tft.fillScreen(TFT_BLACK);
}

void loop() {
    static unsigned long lastFrameTime = 0;
    static int currentFrame = 0;
    static bool forward = true;
    
    if (millis() - lastFrameTime >= FRAME_DELAY) {
        drawBmp(eyeFrames[currentFrame], 0, 0, 1, 1);
        
        if (forward) {
            currentFrame++;
            if (currentFrame >= TOTAL_FRAMES) {
                currentFrame = TOTAL_FRAMES - 2;
                forward = false;
            }
        } else {
            currentFrame--;
            if (currentFrame < 0) {
                currentFrame = 1;
                forward = true;
            }
        }
        
        lastFrameTime = millis();
    }
}

Post the bmp file. I will guess you converted the color palette when you opened then saved the file. Try changing the file back to BMP, (16?) color palette.

still the color is not the original picture

#include <User_Setup.h>
#include <TFT_eSPI.h>
#include <SPIFFS.h>

TFT_eSPI tft = TFT_eSPI();

const int FRAME_DELAY = 100;
const char* eyeFrames[] = {"/eyes.bmp", "/eyes2.bmp", "/eyes3.bmp", "/eyes4.bmp"};
const int TOTAL_FRAMES = 4;

struct BMPHeader {
    uint32_t fileSize;
    uint32_t reserved;
    uint32_t dataOffset;
    uint32_t headerSize;
    int32_t width;
    int32_t height;
    uint16_t planes;
    uint16_t bitsPerPixel;    // Harus 32 untuk ARGB
    uint32_t compression;
    uint32_t imageSize;
    int32_t xPixelsPerM;
    int32_t yPixelsPerM;
    uint32_t colorsUsed;
    uint32_t colorsImportant;
};

bool readBMPHeader(File &f, BMPHeader &header) {
    // Check BMP signature
    if (f.read() != 'B' || f.read() != 'M') {
        return false;
    }
    
    header.fileSize = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.reserved = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.dataOffset = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.headerSize = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.width = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.height = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.planes = f.read() | (f.read() << 8);
    header.bitsPerPixel = f.read() | (f.read() << 8);
    header.compression = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.imageSize = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.xPixelsPerM = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.yPixelsPerM = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.colorsUsed = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    header.colorsImportant = f.read() | (f.read() << 8) | (f.read() << 16) | (f.read() << 24);
    
    return true;
}

void drawBMP(const char *filename, int16_t x, int16_t y) {
    File f = SPIFFS.open(filename, "r");
    if (!f) {
        Serial.println("Failed to open file");
        return;
    }

    BMPHeader header;
    if (!readBMPHeader(f, header)) {
        Serial.println("Not a BMP file");
        f.close();
        return;
    }

    // Verify that we have a 32-bit BMP
    if (header.bitsPerPixel != 32) {
        Serial.println("Not a 32-bit BMP file");
        f.close();
        return;
    }

    // Skip to start of pixel data
    f.seek(header.dataOffset);

    // Calculate row size with padding (4 byte alignment)
    int rowSize = ((4 * header.width + 3) & ~3);
    uint8_t *line = new uint8_t[rowSize];
    if (!line) {
        Serial.println("Unable to allocate memory");
        f.close();
        return;
    }

    // Create display buffer for one row
    uint16_t *displayLine = new uint16_t[header.width];
    if (!displayLine) {
        delete[] line;
        Serial.println("Unable to allocate display buffer");
        f.close();
        return;
    }

    // Read image data bottom to top
    for (int i = header.height - 1; i >= 0; i--) {
        // Read entire line with padding
        if (f.read(line, rowSize) != rowSize) {
            Serial.println("Error reading line");
            break;
        }

        // Convert line to RGB565
        for (int j = 0; j < header.width; j++) {
            // BMP 32-bit ARGB format
            uint8_t b = line[j * 4];      // Blue is at offset 0
            uint8_t g = line[j * 4 + 1];  // Green is offset by 1
            uint8_t r = line[j * 4 + 2];  // Red is offset by 2
            uint8_t a = line[j * 4 + 3];  // Alpha is offset by 3

            // If pixel is not fully transparent
            if (a > 0) {
                // Apply alpha blending
                r = (r * a) >> 8;
                g = (g * a) >> 8;
                b = (b * a) >> 8;

                // Convert to RGB565 format
                uint16_t pixel = ((r & 0xF8) << 8) |  // 5 bits red
                               ((g & 0xFC) << 3) |     // 6 bits green
                               (b >> 3);               // 5 bits blue

                displayLine[j] = pixel;
            } else {
                // For fully transparent pixels, use background color
                displayLine[j] = TFT_BLACK; // or any other background color
            }
        }

        // Push the line to display
        if (y + i >= 0 && y + i < tft.height()) {
            tft.pushImage(x, y + i, header.width, 1, displayLine);
        }
    }

    delete[] line;
    delete[] displayLine;
    f.close();
}

void setup() {
    Serial.begin(115200);
    
    if (!SPIFFS.begin(true)) {
        Serial.println("SPIFFS Mount Failed");
        return;
    }

    tft.init();
    tft.setRotation(1);
    tft.fillScreen(TFT_BLACK);
}

void loop() {
    static unsigned long lastFrameTime = 0;
    static int currentFrame = 0;
    static bool forward = true;
    
    unsigned long currentTime = millis();
    if (currentTime - lastFrameTime >= FRAME_DELAY) {
        drawBMP(eyeFrames[currentFrame], 0, 0);
        
        if (forward) {
            currentFrame++;
            if (currentFrame >= TOTAL_FRAMES) {
                currentFrame = TOTAL_FRAMES - 2;
                forward = false;
            }
        } else {
            currentFrame--;
            if (currentFrame < 0) {
                currentFrame = 1;
                forward = true;
            }
        }
        
        lastFrameTime = currentTime;
    }
}

user.setup.h

#ifndef USER_SETUP_H
#define USER_SETUP_H

// Setup untuk ST7735S
#define ST7735_DRIVER

// Untuk sebagian besar ST7735S, orientasi ini biasanya tepat
#define TFT_WIDTH  80
#define TFT_HEIGHT 160

// Color depth 16-bit (RGB565)
#define COLOR_DEPTH 16

// PENTING: setting warna untuk ST7735S
#define TFT_RGB  // Ubah ke TFT_RGB jika warna masih terbalik

#define TFT_ROTATION 3  // Rotasi 3: orientasi layar horizontal dengan pin di bawah

// Pin SPI default - sesuaikan dengan wiring Anda
#define TFT_MOSI 19  // Pin data SPI (DIN)
#define TFT_SCLK 18  // Pin clock SPI (CLK)
#define TFT_CS   0   // Pin Chip Select
#define TFT_DC   2   // Pin Data/Command
#define TFT_RST  4   // Pin Reset (gunakan -1 jika tidak ada)

// Optimize speed
#define SPI_FREQUENCY  27000000
#define SPI_READ_FREQUENCY  20000000

// Offset layar - sesuaikan jika gambar tidak tepat di tengah
#define TFT_INVERSION_ON
#define ST7735_GREENTAB160x80
//#define ST7735_GREENTAB
//#define ST7735_BLACKTAB

// Font default (opsional)
#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH

#endif

Do you have the image file?