#include <LovyanGFX.hpp>

// 外部图像数据声明
extern const uint8_t rgb888[];    // RGB888格式图像数据
extern const uint8_t bgr888[];    // BGR888格式图像数据
extern const uint16_t swap565[];  // 字节交换的RGB565格式图像数据
extern const uint16_t rgb565[];   // RGB565格式图像数据
extern const uint8_t rgb332[];    // RGB332格式图像数据

static constexpr int image_width = 33;   // 图像宽度
static constexpr int image_height = 31; // 图像高度
//----------------------------------------------------------------------------

static LGFX lcd;  // LCD对象

void setup(void)
{
  lcd.init();      // 初始化LCD
  lcd.startWrite(); // 开始写入操作
}

void loop(void)
{

/*
  绘制图像数据有几种不同的方法。

方法1：预先设置绘制区域，然后指定数据长度进行绘制的方法
方法2：指定绘制坐标和宽度高度来绘制数据的方法



方法1：预先设置绘制区域，然后指定数据长度进行绘制的方法

此方法中，使用setWindow/setAddrWindow函数设置绘制区域后，
使用writePixels/pushPixels函数指定图像数据长度进行绘制。

  setWindow( x0, y0, x1, y1 );   // 指定绘制区域。指定左上坐标和右下坐标。
  setAddrWindow( x, y, w, h );   // 指定绘制区域。指定左上坐标和宽度高度。

setWindow指定画面外坐标时的行为不被保证。
setAddrWindow指定绘制范围外时会自动调整到范围内。
 ※ 但需要注意自动调整的结果，实际设置的宽度或高度可能比指定值小。

  writePixels   ( *data, len, swap );  // 绘制图像。(需要事前startWrite，事后endWrite）
  pushPixels    ( *data, len, swap );  // 绘制图像。(startWrite・endWrite不需要）

 ※ writePixels是源自AdafruitGFX的函数，pushPixels是源自TFT_eSPI的函数。
    绘制内容相同，但startWrite/endWrite是否自动进行不同。

第1参数：图像数据指针（根据数据类型判断颜色形式并进行转换。）
第2参数：图像数据的像素数（注意不是字节数。）
第3参数：字节顺序转换标志（省略时使用事前setSwapBytes函数设置的值。）

根据第1参数data的类型进行颜色形式转换。
  uint8_t*  的情况，作为 8bit颜色 RGB332处理。
  uint16_t* 的情况，作为16bit颜色 RGB565处理。
  void*     的情况，作为24bit颜色 RGB888处理。
 ※ （因为没有3字节的原始类型，所以将void*型作为24bit颜色处理。）

 ※ 在LCD上绘制时，会根据LCD的颜色数模式自动进行颜色形式的转换。
*/
  lcd.clear(TFT_DARKGREY);  // 清除屏幕为深灰色
  lcd.setColorDepth(16);    // 将LCD设置为16bit颜色模式。
  lcd.setSwapBytes(true);   // 启用字节顺序转换。
  int len = image_width * image_height;  // 计算像素总数

  // 使用setAddrWindow预先设置图像的宽度和高度，然后用writePixels绘制。
  lcd.setAddrWindow(0, 0, image_width, image_height);         // 设置绘制区域。
  lcd.writePixels((uint16_t*)rgb565, len); // 绘制RGB565的16bit图像数据。

  // 数据和字节顺序转换的指定不一致时，会出现颜色异常。
  lcd.setAddrWindow(0, 40, image_width, image_height);
  // 通过第3参数指定false可以指定字节顺序转换的有无。
  lcd.writePixels((uint16_t*)rgb565, len, false); // RGB565的图像不进行字节顺序转换绘制会导致颜色异常。

  // 绘制区域超出画面外等导致图像的宽度和高度不一致时，绘制结果会损坏。
  lcd.setAddrWindow(-1, 80, image_width, image_height); // X坐标为-1（画面外）所以无法正确设置。
  lcd.writePixels((uint16_t*)rgb565, len); // 绘制目标的宽度和图像的宽度不一致导致绘制内容损坏。

  // 数据和类型不一致时，绘制结果也会损坏。
  lcd.setAddrWindow(0, 120, image_width, image_height);
  // 将RGB565的数据故意转换为uint8_t，作为RGB332的8bit颜色处理。
  lcd.writePixels((uint8_t*)rgb565, len);  // 图像形式和类型不一致导致绘制混乱。

  // 数据和类型一致的话，会根据绘制目标的颜色数进行适当的形式转换。
  lcd.setAddrWindow(0, 160, image_width, image_height);
  lcd.writePixels((uint8_t*)rgb332, len);  // RGB332的数据也能在16bit颜色的LCD上正确绘制。


// ※ 向LCD发送图像数据时，从内存中地址较小的数据开始按1Byte单位发送。
//    因此，例如RGB565的16bit型数据直接用uint16_t数组准备的话，发送的顺序上字节顺序会颠倒。
//    这种情况下使用事前setSwapBytes(true)或第3参数指定true，会进行字节顺序转换从而正常绘制。
//    另外，如果准备的图像数据预先做好高低位字节颠倒的状态，则不需要此转换，速度方面有利。

  lcd.setAddrWindow(40,  0, image_width, image_height);
  lcd.writePixels((uint16_t*)swap565, len, false); // 预先高低位颠倒的16bit数据的情况下禁用字节顺序转换。

  lcd.setAddrWindow(40, 40, image_width, image_height);
  lcd.writePixels((uint16_t*)swap565, len, true);  // 相反，对预先高低位颠倒的数据进行字节顺序转换会导致颜色异常。

  lcd.setAddrWindow(40, 80, image_width, image_height);
  lcd.writePixels((void*)rgb888, len, true);  // 24bit数据同样，RGB888的蓝色在低位的数据需要字节顺序转换。

  lcd.setAddrWindow(40, 120, image_width, image_height);
  lcd.writePixels((void*)bgr888, len, false);  // 同样，BGR888的红色在低位的数据不需要字节顺序转换。

  lcd.setAddrWindow(40, 160, image_width, image_height);
  lcd.writePixels((void*)bgr888, len, true);  // 设置错误的话，颜色会异常。（红色和蓝色交换）

  lcd.display();  // 显示内容
  delay(4000);    // 延时4秒
  lcd.clear(TFT_DARKGREY);  // 清除屏幕

/*
方法2：指定绘制坐标和宽度高度来绘制数据的方法

此方法中，使用pushImage函数指定绘制区域和绘制数据进行绘制。

  pushImage( x, y, w, h, *data);                  // 在指定坐标绘制图像。

与方法1不同，即使指定超出画面的坐标也不会导致绘制混乱。（超出的部分不会被绘制。）
与方法1不同，没有指定字节顺序转换的参数，所以需要事前setSwapBytes的设置。
另外与方法1相同，根据data的类型进行颜色转换。
*/

  lcd.setSwapBytes(true); // 启用字节顺序转换。

  // 指定绘制目标坐标和图像的宽度高度绘制图像数据。
  lcd.pushImage(   0, 0, image_width, image_height, (uint16_t*)rgb565); // 绘制RGB565的16bit图像数据。

  // 数据和字节顺序转换的指定不一致时，颜色会异常。
  lcd.pushImage(   0, 40, image_width, image_height, (uint16_t*)swap565); // NG. 对字节顺序转换完成的数据进行字节顺序转换会导致颜色异常。

  // 即使绘制区域超出画面外等，绘制结果也不会损坏。
  lcd.pushImage(-1, 80, image_width, image_height, (uint16_t*)rgb565); // 即使指定X坐标-1（画面外）绘制也不会混乱。

  // 数据和类型不一致时，绘制结果会损坏。
  lcd.pushImage(0, 120, image_width, image_height, (uint8_t*)rgb565); // 将RGB565的数据转换为uint8_t，作为RGB332处理会导致绘制混乱。

  // 数据和类型一致的话，会进行适当的形式转换。
  lcd.pushImage(0, 160, image_width, image_height, (uint8_t*)rgb332); // RGB332的数据也能正确绘制。


  lcd.setSwapBytes(false);   // 禁用字节顺序转换。
  lcd.pushImage( 40,   0, image_width, image_height, (uint8_t* )rgb332);  // good. RGB332的数据不受字节顺序转换影响。
  lcd.pushImage( 40,  40, image_width, image_height, (uint16_t*)rgb565);  // NG. RGB565的数据需要字节顺序转换。
  lcd.pushImage( 40,  80, image_width, image_height, (void*    )rgb888);  // NG. RGB888的数据需要字节顺序转换。
  lcd.pushImage( 40, 120, image_width, image_height, (uint16_t*)swap565); // good. 字节顺序转换完成的RGB565数据不会颜色异常。
  lcd.pushImage( 40, 160, image_width, image_height, (void*    )bgr888);  // good. 字节顺序转换完成的RGB888数据不会颜色异常。

  lcd.setSwapBytes(true);   // 启用字节顺序转换。
  lcd.pushImage( 80,   0, image_width, image_height, (uint8_t* )rgb332);  // good. RGB332的数据不受字节顺序转换影响。
  lcd.pushImage( 80,  40, image_width, image_height, (uint16_t*)rgb565);  // good. 字节顺序转换启用的话RGB565的数据不会颜色异常。
  lcd.pushImage( 80,  80, image_width, image_height, (void*    )rgb888);  // good. 字节顺序转换启用的话RGB888的数据不会颜色异常。
  lcd.pushImage( 80, 120, image_width, image_height, (uint16_t*)swap565); // NG. 对字节顺序转换完成的数据进行字节顺序转换会导致颜色异常。
  lcd.pushImage( 80, 160, image_width, image_height, (void*    )bgr888);  // NG. 对字节顺序转换完成的数据进行字节顺序转换会导致颜色异常。

// 作为数据的类型，也可以使用lgfx::命名空间中定义的类型。
// 转换为这些类型的情况下setSwapBytes的设置被忽略。
  lcd.pushImage(120,   0, image_width, image_height, (lgfx:: rgb332_t*) rgb332); // good  8bit数据
  lcd.pushImage(120,  40, image_width, image_height, (lgfx:: rgb565_t*) rgb565); // good 16bit数据
  lcd.pushImage(120,  80, image_width, image_height, (lgfx:: rgb888_t*) rgb888); // good 24bit数据
  lcd.pushImage(120, 120, image_width, image_height, (lgfx::swap565_t*)swap565); // good 字节顺序转换完成16bit数据
  lcd.pushImage(120, 160, image_width, image_height, (lgfx:: bgr888_t*) bgr888); // good 字节顺序转换完成24bit数据

// 第6参数可以指定透明色。指定为透明的颜色的部分不会被绘制。
  lcd.pushImage(160,   0, image_width, image_height, (lgfx:: rgb332_t*) rgb332, 0);                   // 指定黑色透明
  lcd.pushImage(160,  40, image_width, image_height, (lgfx:: rgb565_t*) rgb565, (uint8_t)0xE0);       // 指定红色透明
  lcd.pushImage(160,  80, image_width, image_height, (lgfx:: rgb888_t*) rgb888, (uint16_t)0x07E0);    // 指定绿色透明
  lcd.pushImage(160, 120, image_width, image_height, (lgfx::swap565_t*)swap565, (uint32_t)0x0000FFU); // 指定蓝色透明
  lcd.pushImage(160, 160, image_width, image_height, (lgfx:: bgr888_t*) bgr888, TFT_WHITE);           // 指定白色透明

  lcd.display();
  delay(4000);
  lcd.clear(TFT_DARKGREY);

// 使用pushImageRotateZoom函数可以旋转缩放图像进行绘制。
  for (int angle = 0; angle <= 360; ++angle) {
    lcd.pushImageRotateZoom
      ( lcd.width()  >> 2  // 绘制目标的中心坐标X
      , lcd.height() >> 1  // 绘制目标的中心坐标Y
      , image_width  >> 1  // 图像的中心坐标X
      , image_height >> 1  // 图像的中心坐标Y
      , angle              // 旋转角度
      , 3.0                // X方向的绘制倍率 (负数指定可以反转)
      , 3.0                // Y方向的绘制倍率 (负数指定可以反转)
      , image_width        // 图像数据的宽度
      , image_height       // 图像数据的高度
      , rgb332             // 图像数据的指针
      );

// 使用pushImageRotateZoomWithAA函数可以启用抗锯齿。
    lcd.pushImageRotateZoomWithAA
      ( lcd.width()*3>> 2
      , lcd.height() >> 1
      , image_width  >> 1
      , image_height >> 1
      , angle
      , 3.0
      , 3.0
      , image_width
      , image_height
      , rgb332
      );

    if ((angle % 36) == 0) { lcd.display(); }
  }

  lcd.clear(TFT_DARKGREY);

// 使用pushImageAffine函数可以通过仿射变换变形图像进行绘制。
// 仿射变换的参数用float型数组指定。
  {
    float matrix[6] = // 等倍显示
      { 1.0,  0.0,  (float)lcd.width()  / 2
      , 0.0,  1.0,  (float)lcd.height() / 2 };
    lcd.pushImageAffine(matrix, image_width, image_height, rgb332);
  }

  lcd.display();
  delay(1000);
  lcd.clear(TFT_DARKGREY);

  {
    float matrix[6] = // 横2倍显示
      { 2.0,  0.0,  (float)lcd.width()  / 2
      , 0.0,  1.0,  (float)lcd.height() / 2 };
    lcd.pushImageAffine(matrix, image_width, image_height, rgb332);
  }

  lcd.display();
  delay(1000);
  lcd.clear(TFT_DARKGREY);

  {
    float matrix[6] = // 纵2倍显示
      { 1.0,  0.0,  (float)lcd.width()  / 2
      , 0.0,  2.0,  (float)lcd.height() / 2 };
    lcd.pushImageAffine(matrix, image_width, image_height, rgb332);
  }

  lcd.display();
  delay(1000);
  lcd.clear(TFT_DARKGREY);

  {
    float matrix[6] = // 斜向变形
      { 1.0, -0.4,  (float)lcd.width()  / 2
      , 0.0,  1.0,  (float)lcd.height() / 2 };
    lcd.pushImageAffine(matrix, image_width, image_height, rgb332);
  }

  lcd.display();
  delay(1000);
  lcd.clear(TFT_DARKGREY);

  // 使用pushImageAffineWithAA函数可以启用抗锯齿。
  {
    float matrix[6] =
      { 1.0,  0.0,  (float)lcd.width()  / 2
      , 0.0,  1.0,  (float)lcd.height() / 2 };
    for (int i = -300; i < 300; i++) {
      float f = (float)i / 100;
      matrix[1] = f;
      matrix[3] = f;
      lcd.pushImageAffineWithAA(matrix, image_width, image_height, rgb332);

      if ((i % 30) == 0) { lcd.display(); }
    }
  }
}


//----------------------------------------------------------------------------
// 以下是不同格式的图像数据定义
// RGB888格式图像数据
#define R 0x00,0x00,0xFF,
#define G 0x00,0xFF,0x00,
#define B 0xFF,0x00,0x00,
#define C 0xFF,0xFF,0x00,
#define M 0xFF,0x00,0xFF,
#define Y 0x00,0xFF,0xFF,
#define W 0xFF,0xFF,0xFF,
#define _ 0x00,0x00,0x00,
constexpr uint8_t rgb888[] = {
#include "image.h"
};
#undef R
#undef G
#undef B
#undef C
#undef M
#undef Y
#undef W
#undef _
//----------------------------------------------------------------------------
// BGR888格式图像数据
#define R 0xFF,0x00,0x00,
#define G 0x00,0xFF,0x00,
#define B 0x00,0x00,0xFF,
#define C 0x00,0xFF,0xFF,
#define M 0xFF,0x00,0xFF,
#define Y 0xFF,0xFF,0x00,
#define W 0xFF,0xFF,0xFF,
#define _ 0x00,0x00,0x00,
constexpr uint8_t bgr888[] = {
#include "image.h"
};
#undef R
#undef G
#undef B
#undef C
#undef M
#undef Y
#undef W
#undef _
//----------------------------------------------------------------------------
// 字节交换的RGB565格式图像数据
#define R 0x00F8,
#define G 0xE007,
#define B 0x1F00,
#define C 0xFF07,
#define M 0x1FF8,
#define Y 0xE0FF,
#define W 0xFFFF,
#define _ 0x0000,
constexpr uint16_t swap565[] = {
#include "image.h"
};
#undef R
#undef G
#undef B
#undef C
#undef M
#undef Y
#undef W
#undef _
//----------------------------------------------------------------------------
// RGB565格式图像数据
#define R 0xF800,
#define G 0x07E0,
#define B 0x001F,
#define C 0x07FF,
#define M 0xF81F,
#define Y 0xFFE0,
#define W 0xFFFF,
#define _ 0x0000,
constexpr uint16_t rgb565[] = {
#include "image.h"
};
#undef R
#undef G
#undef B
#undef C
#undef M
#undef Y
#undef W
#undef _
//----------------------------------------------------------------------------
// RGB332格式图像数据
#define R 0xE0,
#define G 0x1C,
#define B 0x03,
#define C 0x1F,
#define M 0xE3,
#define Y 0xFC,
#define W 0xFF,
#define _ 0x00,
constexpr uint8_t rgb332[] = {
#include "image.h"
};
#undef R
#undef G
#undef B
#undef C
#undef M
#undef Y
#undef W
#undef _