#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <LittleFS.h>
#include <Adafruit_GC9A01A.h>
#include <Adafruit_GFX.h>
#include <SPI.h>

// Параметры Wi-Fi
const char* ssid = "Keenetic-4342";
const char* password = "Gf4HsZTH";

// Пины подключения дисплея
#define TFT_CS   D8      // GPIO15
#define TFT_DC   D2      // GPIO4
#define TFT_BL   D6      // GPIO12
#define TFT_RESET -1     // Если дисплей не имеет пина сброса, установите -1

// Инициализация дисплея
Adafruit_GC9A01A tft = Adafruit_GC9A01A(TFT_CS, TFT_DC, MOSI, SCK, TFT_RESET);

// Инициализация веб-сервера на порту 80
ESP8266WebServer server(80);

// Функция для перестановки красной и синей составляющих для 16-битных цветов
uint16_t swapRB(uint16_t color) {
  return ((color & 0x001F) << 11) | (color & 0x07E0) | ((color & 0xF800) >> 11);
}

void setup() {
  // Инициализация последовательного порта
  Serial.begin(115200);
  delay(1000); // Задержка для стабильности

  // Инициализация дисплея
  tft.begin();
  tft.setRotation(0); // Настройте поворот дисплея при необходимости
  tft.fillScreen(GC9A01A_BLACK); // Очистка дисплея

  // Включение подсветки дисплея
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, HIGH); // Включаем подсветку

  // Подключение к Wi-Fi сети
  WiFi.begin(ssid, password);
  Serial.print("Connecting to Wi-Fi...");
  tft.setTextSize(2);
  tft.setTextColor(GC9A01A_WHITE);
  tft.setCursor(10, 10);
  tft.print("Connecting to WiFi");

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    tft.print(".");
  }

  Serial.println("\nWi-Fi connected");
  Serial.print("IP-адрес: ");
  Serial.println(WiFi.localIP());

  // Отображение IP-адреса на дисплее
  tft.fillScreen(GC9A01A_BLACK);
  tft.setCursor(10, 10);
  tft.setTextColor(GC9A01A_WHITE);
  tft.println("WiFi ready!");
  tft.setCursor(10, 30);
  tft.println(WiFi.localIP());

  // Инициализация файловой системы LittleFS
  if (!LittleFS.begin()) {
    Serial.println("Error mounting LittleFS");
    tft.setCursor(10, 50);
    tft.setTextColor(GC9A01A_RED);
    tft.println("LittleFS mount failed");
    return;
  }

  // Настройка маршрутов веб-сервера
  server.on("/", HTTP_GET, handleRoot);        // Главная страница
  server.on("/list", HTTP_GET, listFiles);     // Маршрут для отображения списка файлов
  server.on("/1.bmp", HTTP_GET, serveBMP);     // Маршрут для обслуживания BMP-изображения

  server.begin();
  Serial.println("Web server ready");
  tft.setCursor(10, 50);
  tft.setTextColor(GC9A01A_GREEN);
  tft.println("Web ready");

  // Отображение изображения на дисплее
  displayBMP("/1.bmp");
}

void loop() {
  server.handleClient();
}

// Функция для отображения главной страницы
void handleRoot() {
  String html = "<!DOCTYPE html><html lang=\"ru\">";
  html += "<head><meta charset=\"UTF-8\"><title>ESP8266 Web Server</title></head>";
  html += "<body>";
  html += "<h1>Привет из ESP8266 Web Server</h1>";
  html += "<p><a href=\"/list\">Список файлов</a></p>";
  html += "<h2>Изображение 1.bmp:</h2>";
  html += "<img src=\"/1.bmp\" alt=\"1.bmp\" style=\"max-width:100%; height:auto;\">";
  html += "</body></html>";
  
  server.send(200, "text/html", html);
}

// Функция для отображения списка файлов в LittleFS
void listFiles(){
  String message = "<h1>Список файлов в LittleFS</h1><ul>";
  
  // Открываем корневой каталог в режиме чтения
  File root = LittleFS.open("/", "r");
  
  File file = root.openNextFile();
  while(file){
    if(file.isDirectory()){
      message += "<li>DIR: " + String(file.name()) + "</li>";
    } else {
      message += "<li>FILE: " + String(file.name()) + " - " + String(file.size()) + " bytes</li>";
    }
    file = root.openNextFile();
  }
  message += "</ul>";
  
  server.send(200, "text/html", message);
}

// Функция для обслуживания файла 1.bmp через веб-сервер
void serveBMP(){
  const char* path = "/1.bmp";  // Путь к файлу в LittleFS
  
  if(LittleFS.exists(path)){
    File file = LittleFS.open(path, "r");
    if(file){
      // Установка заголовка Content-Type для BMP
      server.streamFile(file, "image/bmp");
      file.close();
    }
    else{
      server.send(500, "text/plain", "Ошибка открытия файла.");
    }
  }
  else{
    server.send(404, "text/plain", "Файл не найден.");
  }
}

// Функция для отображения BMP-изображения на дисплее
void displayBMP(const char* path){
  // Проверка существования файла
  if(!LittleFS.exists(path)){
    Serial.println("Файл не найден!");
    tft.setCursor(10, 70);
    tft.setTextColor(GC9A01A_RED);
    tft.println("BMP файл не найден");
    return;
  }

  File bmpFile = LittleFS.open(path, "r");
  if(!bmpFile){
    Serial.println("Ошибка открытия BMP файла");
    tft.setCursor(10, 70);
    tft.setTextColor(GC9A01A_RED);
    tft.println("Ошибка открытия BMP файла");
    return;
  }

  // Чтение заголовка BMP
  uint16_t type = bmpFile.read() | (bmpFile.read() << 8);
  if(type != 0x4D42){ // Проверка на "BM"
    Serial.println("Не BMP файл");
    tft.setCursor(10, 70);
    tft.setTextColor(GC9A01A_RED);
    tft.println("Не BMP файл");
    bmpFile.close();
    return;
  }

  // Пропуск остальной части заголовка до начала пикселей
  bmpFile.seek(10);
  uint32_t dataOffset = 0;
  dataOffset |= bmpFile.read(); 
  dataOffset |= bmpFile.read() << 8;
  dataOffset |= bmpFile.read() << 16;
  dataOffset |= bmpFile.read() << 24;

  bmpFile.seek(18);
  uint32_t width = 0;
  width |= bmpFile.read();
  width |= bmpFile.read() << 8;
  width |= bmpFile.read() << 16;
  width |= bmpFile.read() << 24;

  uint32_t height = 0;
  height |= bmpFile.read();
  height |= bmpFile.read() << 8;
  height |= bmpFile.read() << 16;
  height |= bmpFile.read() << 24;

  bmpFile.seek(28);
  uint16_t bitsPerPixel = bmpFile.read() | (bmpFile.read() << 8);

  if(bitsPerPixel != 16 && bitsPerPixel != 24){
    Serial.println("Битов на пиксель не поддерживается");
    tft.setCursor(10, 70);
    tft.setTextColor(GC9A01A_RED);
    tft.println("Битов на пиксель не поддерживается");
    bmpFile.close();
    return;
  }

  // Пропуск до начала данных пикселей
  bmpFile.seek(dataOffset);

  Serial.printf("Загрузка BMP: %s (%lux%lu)\n", path, width, height);

  // Настройка координат отображения (центрирование изображения)
  int16_t x0 = (tft.width() - width) / 2;
  int16_t y0 = (tft.height() - height) / 2;

  if(x0 < 0) x0 = 0;
  if(y0 < 0) y0 = 0;

  // Чтение и отображение пикселей
  for(uint32_t y = 0; y < height; y++){
    for(uint32_t x = 0; x < width; x++){
      uint16_t color = 0;
      if(bitsPerPixel == 16){
        // Читаем 16-битный цвет (BGR565) и меняем порядок на RGB565
        uint16_t bgr = bmpFile.read() | (bmpFile.read() << 8); // BGR565
        color = swapRB(bgr); // RGB565
      }
      else{
        // 24 бит: BGR888 -> RGB565
        uint8_t b = bmpFile.read();
        uint8_t g = bmpFile.read();
        uint8_t r = bmpFile.read();
        color = tft.color565(r, g, b);
      }
      tft.drawPixel(x0 + x, y0 + y, color);
    }
    // Пропуск возможных выравнивающих байтов (каждая строка BMP выравнивается до 4 байт)
    if(bitsPerPixel == 24){
      uint32_t rowSize = (width * 3 + 3) & (~3); // Выравнивание до 4 байт
      uint32_t padding = rowSize - (width * 3);
      for(uint32_t p = 0; p < padding; p++) {
        bmpFile.read(); // Пропуск выравнивающих байтов
      }
    }
  }

  bmpFile.close();

}

Оптимизируй загрузку изображения