forked from gronod/squeezelite-esp32
applied platformio structure
This commit is contained in:
319
lib/display/core/gds.c
Normal file
319
lib/display/core/gds.c
Normal file
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 Tara Keeling
|
||||
* 2020 Philippe G.
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "gds.h"
|
||||
#include "gds_private.h"
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||
#define LEDC_SPEED_MODE LEDC_LOW_SPEED_MODE
|
||||
#else
|
||||
#define LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE
|
||||
#endif
|
||||
|
||||
static struct GDS_Device Display;
|
||||
static struct GDS_BacklightPWM PWMConfig;
|
||||
|
||||
static char TAG[] = "gds";
|
||||
|
||||
struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM* PWM ) {
|
||||
if (!Driver) return NULL;
|
||||
if (PWM) PWMConfig = *PWM;
|
||||
|
||||
for (int i = 0; DetectFunc[i]; i++) {
|
||||
if (DetectFunc[i](Driver, &Display)) {
|
||||
if (PWM && PWM->Init) {
|
||||
ledc_timer_config_t PWMTimer = {
|
||||
.duty_resolution = LEDC_TIMER_13_BIT,
|
||||
.freq_hz = 5000,
|
||||
.speed_mode = LEDC_SPEED_MODE,
|
||||
.timer_num = PWMConfig.Timer,
|
||||
};
|
||||
ledc_timer_config(&PWMTimer);
|
||||
}
|
||||
ESP_LOGD(TAG, "Detected driver %p with PWM %d", &Display, PWM ? PWM->Init : 0);
|
||||
return &Display;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void GDS_ClearExt(struct GDS_Device* Device, bool full, ...) {
|
||||
bool commit = true;
|
||||
|
||||
if (full) {
|
||||
GDS_Clear( Device, GDS_COLOR_BLACK );
|
||||
} else {
|
||||
va_list args;
|
||||
va_start(args, full);
|
||||
commit = va_arg(args, int);
|
||||
int x1 = va_arg(args, int), y1 = va_arg(args, int), x2 = va_arg(args, int), y2 = va_arg(args, int);
|
||||
if (x2 < 0) x2 = Device->Width - 1;
|
||||
if (y2 < 0) y2 = Device->Height - 1;
|
||||
GDS_ClearWindow( Device, x1, y1, x2, y2, GDS_COLOR_BLACK );
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
if (commit) GDS_Update(Device);
|
||||
}
|
||||
|
||||
void GDS_Clear( struct GDS_Device* Device, int Color ) {
|
||||
if (Color == GDS_COLOR_BLACK) memset( Device->Framebuffer, 0, Device->FramebufferSize );
|
||||
else if (Device->Depth == 1) memset( Device->Framebuffer, 0xff, Device->FramebufferSize );
|
||||
else if (Device->Depth == 4) memset( Device->Framebuffer, Color | (Color << 4), Device->FramebufferSize );
|
||||
else if (Device->Depth == 8) memset( Device->Framebuffer, Color, Device->FramebufferSize );
|
||||
else GDS_ClearWindow(Device, 0, 0, -1, -1, Color);
|
||||
Device->Dirty = true;
|
||||
}
|
||||
|
||||
#define CLEAR_WINDOW(x1,y1,x2,y2,F,W,C,T,N) \
|
||||
for (int y = y1; y <= y2; y++) { \
|
||||
T *Ptr = (T*) F + (y * W + x1)*N; \
|
||||
for (int c = (x2 - x1)*N; c-- >= 0; *Ptr++ = C); \
|
||||
}
|
||||
|
||||
void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
|
||||
// -1 means up to width/height
|
||||
if (x2 < 0) x2 = Device->Width - 1;
|
||||
if (y2 < 0) y2 = Device->Height - 1;
|
||||
|
||||
// driver can provide own optimized clear window
|
||||
if (Device->ClearWindow) {
|
||||
Device->ClearWindow( Device, x1, y1, x2, y2, Color );
|
||||
} else if (Device->Depth == 1) {
|
||||
// single shot if we erase all screen
|
||||
if (x2 - x1 == Device->Width - 1 && y2 - y1 == Device->Height - 1) {
|
||||
memset( Device->Framebuffer, Color == GDS_COLOR_BLACK ? 0 : 0xff, Device->FramebufferSize );
|
||||
} else {
|
||||
uint8_t _Color = Color == GDS_COLOR_BLACK ? 0: 0xff;
|
||||
uint8_t Width = Device->Width >> 3;
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
// try to do byte processing as much as possible
|
||||
for (int r = y1; r <= y2;) {
|
||||
int c = x1;
|
||||
// for a row that is not on a boundary, no optimization possible
|
||||
while (r & 0x07 && r <= y2) {
|
||||
for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
|
||||
r++;
|
||||
}
|
||||
// go fast if we have more than 8 lines to write
|
||||
if (r + 8 <= y2) {
|
||||
memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
|
||||
r += 8;
|
||||
} else while (r <= y2) {
|
||||
for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
|
||||
r++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} if (Device->Depth == 4) {
|
||||
if (x2 - x1 == Device->Width - 1 && y2 - y1 == Device->Height - 1) {
|
||||
// we assume color is 0..15
|
||||
memset( Device->Framebuffer, Color | (Color << 4), Device->FramebufferSize );
|
||||
} else {
|
||||
uint8_t _Color = Color | (Color << 4);
|
||||
int Width = Device->Width;
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
// try to do byte processing as much as possible
|
||||
for (int r = y1; r <= y2; r++) {
|
||||
int c = x1;
|
||||
if (c & 0x01) Device->DrawPixelFast( Device, c++, r, Color);
|
||||
int chunk = (x2 - c + 1) >> 1;
|
||||
memset(optr + ((r * Width + c) >> 1), _Color, chunk);
|
||||
if (c + chunk <= x2) Device->DrawPixelFast( Device, x2, r, Color);
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth == 8) {
|
||||
CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint8_t,1);
|
||||
} else if (Device->Depth == 16) {
|
||||
CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint16_t,1);
|
||||
} else if (Device->Depth == 24) {
|
||||
CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint8_t,3);
|
||||
} else {
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
Device->DrawPixelFast( Device, x, y, Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure diplay will do update
|
||||
Device->Dirty = true;
|
||||
}
|
||||
|
||||
void GDS_Update( struct GDS_Device* Device ) {
|
||||
if (Device->Dirty) Device->Update( Device );
|
||||
Device->Dirty = false;
|
||||
}
|
||||
|
||||
bool GDS_Reset( struct GDS_Device* Device ) {
|
||||
if ( Device->RSTPin >= 0 ) {
|
||||
gpio_set_level( Device->RSTPin, 0 );
|
||||
vTaskDelay( pdMS_TO_TICKS( 100 ) );
|
||||
gpio_set_level( Device->RSTPin, 1 );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint32_t YBit = ( Y & 0x07 );
|
||||
uint8_t* FBOffset;
|
||||
|
||||
/*
|
||||
* We only need to modify the Y coordinate since the pitch
|
||||
* of the screen is the same as the width.
|
||||
* Dividing Y by 8 gives us which row the pixel is in but not
|
||||
* the bit position.
|
||||
*/
|
||||
Y>>= 3;
|
||||
|
||||
FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
|
||||
|
||||
if ( Color == GDS_COLOR_XOR ) {
|
||||
*FBOffset ^= BIT( YBit );
|
||||
} else {
|
||||
*FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
|
||||
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel4FastHigh( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
|
||||
*FBOffset = X & 0x01 ? ((*FBOffset & 0xf0) | (Color & 0x0f)) : (*FBOffset & 0x0f) | ((Color & 0x0f) << 4);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
Device->Framebuffer[Y * Device->Width + X] = Color;
|
||||
}
|
||||
|
||||
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
|
||||
static void IRAM_ATTR DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
|
||||
*FBOffset = __builtin_bswap16(Color);
|
||||
}
|
||||
|
||||
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
|
||||
static void IRAM_ATTR DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
|
||||
}
|
||||
|
||||
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
|
||||
static void IRAM_ATTR DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
|
||||
}
|
||||
|
||||
bool GDS_Init( struct GDS_Device* Device ) {
|
||||
|
||||
if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8);
|
||||
else Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth);
|
||||
|
||||
// set the proper DrawPixel function if not already set by driver
|
||||
if (!Device->DrawPixelFast) {
|
||||
if (Device->Depth == 1) Device->DrawPixelFast = DrawPixel1Fast;
|
||||
else if (Device->Depth == 4 && Device->HighNibble) Device->DrawPixelFast = DrawPixel4FastHigh;
|
||||
else if (Device->Depth == 4) Device->DrawPixelFast = DrawPixel4Fast;
|
||||
else if (Device->Depth == 8) Device->DrawPixelFast = DrawPixel8Fast;
|
||||
else if (Device->Depth == 16) Device->DrawPixelFast = DrawPixel16Fast;
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) Device->DrawPixelFast = DrawPixel18Fast;
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) Device->DrawPixelFast = DrawPixel24Fast;
|
||||
}
|
||||
|
||||
// allocate FB unless explicitely asked not to
|
||||
if (!(Device->Alloc & GDS_ALLOC_NONE)) {
|
||||
if ((Device->Alloc & GDS_ALLOC_IRAM) || ((Device->Alloc & GDS_ALLOC_IRAM_SPI) && Device->IF == GDS_IF_SPI)) {
|
||||
Device->Framebuffer = heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
|
||||
} else {
|
||||
Device->Framebuffer = calloc( 1, Device->FramebufferSize );
|
||||
}
|
||||
NullCheck( Device->Framebuffer, return false );
|
||||
}
|
||||
|
||||
if (Device->Backlight.Pin >= 0) {
|
||||
Device->Backlight.Channel = PWMConfig.Channel++;
|
||||
Device->Backlight.PWM = PWMConfig.Max - 1;
|
||||
|
||||
ledc_channel_config_t PWMChannel = {
|
||||
.channel = Device->Backlight.Channel,
|
||||
.duty = Device->Backlight.PWM,
|
||||
.gpio_num = Device->Backlight.Pin,
|
||||
.speed_mode = LEDC_SPEED_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = PWMConfig.Timer,
|
||||
};
|
||||
|
||||
ledc_channel_config(&PWMChannel);
|
||||
}
|
||||
|
||||
bool Res = Device->Init( Device );
|
||||
if (!Res && Device->Framebuffer) free(Device->Framebuffer);
|
||||
return Res;
|
||||
}
|
||||
|
||||
int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level) {
|
||||
switch(Device->Mode) {
|
||||
case GDS_MONO: return Level;
|
||||
case GDS_GRAYSCALE: return Level >> (8 - Device->Depth);
|
||||
case GDS_RGB332:
|
||||
Level >>= 5;
|
||||
return (Level << 6) | (Level << 3) | (Level >> 1);
|
||||
case GDS_RGB444:
|
||||
Level >>= 4;
|
||||
return (Level << 8) | (Level << 4) | Level;
|
||||
case GDS_RGB555:
|
||||
Level >>= 3;
|
||||
return (Level << 10) | (Level << 5) | Level;
|
||||
case GDS_RGB565:
|
||||
Level >>= 2;
|
||||
return ((Level & ~0x01) << 10) | (Level << 5) | (Level >> 1);
|
||||
case GDS_RGB666:
|
||||
Level >>= 2;
|
||||
return (Level << 12) | (Level << 6) | Level;
|
||||
case GDS_RGB888:
|
||||
return (Level << 16) | (Level << 8) | Level;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
|
||||
if (Device->SetContrast) Device->SetContrast( Device, Contrast );
|
||||
else if (Device->Backlight.Pin >= 0) {
|
||||
Device->Backlight.PWM = PWMConfig.Max * powf(Contrast / 255.0, 3);
|
||||
ledc_set_duty( LEDC_SPEED_MODE, Device->Backlight.Channel, Device->Backlight.PWM );
|
||||
ledc_update_duty( LEDC_SPEED_MODE, Device->Backlight.Channel );
|
||||
}
|
||||
}
|
||||
|
||||
void GDS_SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) { if (Device->SetLayout) Device->SetLayout( Device, Layout ); }
|
||||
void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; }
|
||||
int GDS_GetWidth( struct GDS_Device* Device ) { return Device ? Device->Width : 0; }
|
||||
void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth ) { Device->TextWidth = Device && TextWidth && TextWidth < Device->Width ? TextWidth : Device->Width; }
|
||||
int GDS_GetHeight( struct GDS_Device* Device ) { return Device ? Device->Height : 0; }
|
||||
int GDS_GetDepth( struct GDS_Device* Device ) { return Device ? Device->Depth : 0; }
|
||||
int GDS_GetMode( struct GDS_Device* Device ) { return Device ? Device->Mode : 0; }
|
||||
void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); }
|
||||
void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( Device ); }
|
||||
56
lib/display/core/gds.h
Normal file
56
lib/display/core/gds.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef _GDS_H_
|
||||
#define _GDS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* NOTE for drivers:
|
||||
The build-in DrawPixel(Fast), DrawCBR and ClearWindow have optimized for 1 bit
|
||||
and 4 bits grayscale screen depth and 8, 16, 24 color. For any other type of screen,
|
||||
DrawCBR and ClearWindow default to use DrawPixel, which is very sub-optimal. For
|
||||
other depth, you must supply the DrawPixelFast. The built-in 1 bit depth function
|
||||
are only for screen with vertical framing (1 byte = 8 lines). For example SSD1326 in
|
||||
monochrome mode is not such type of screen, SH1106 and SSD1306 are
|
||||
*/
|
||||
|
||||
// this is an ordered enum, do not change!
|
||||
enum { GDS_MONO = 0, GDS_GRAYSCALE, GDS_RGB332, GDS_RGB444, GDS_RGB555, GDS_RGB565, GDS_RGB666, GDS_RGB888 };
|
||||
|
||||
#define GDS_COLOR_BLACK (0)
|
||||
#define GDS_COLOR_WHITE (-1)
|
||||
#define GDS_COLOR_XOR (256)
|
||||
|
||||
struct GDS_Device;
|
||||
struct GDS_FontDef;
|
||||
struct GDS_BacklightPWM {
|
||||
int Channel, Timer, Max;
|
||||
bool Init;
|
||||
};
|
||||
struct GDS_Layout {
|
||||
bool HFlip, VFlip;
|
||||
bool Rotate;
|
||||
bool Invert;
|
||||
bool ColorSwap;
|
||||
};
|
||||
|
||||
typedef struct GDS_Device* GDS_DetectFunc(char *Driver, struct GDS_Device *Device);
|
||||
|
||||
struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM *PWM );
|
||||
|
||||
void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast );
|
||||
void GDS_DisplayOn( struct GDS_Device* Device );
|
||||
void GDS_DisplayOff( struct GDS_Device* Device );
|
||||
void GDS_Update( struct GDS_Device* Device );
|
||||
void GDS_SetLayout( struct GDS_Device* Device, struct GDS_Layout* Layout);
|
||||
void GDS_SetDirty( struct GDS_Device* Device );
|
||||
int GDS_GetWidth( struct GDS_Device* Device );
|
||||
void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth );
|
||||
int GDS_GetHeight( struct GDS_Device* Device );
|
||||
int GDS_GetDepth( struct GDS_Device* Device );
|
||||
int GDS_GetMode( struct GDS_Device* Device );
|
||||
int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level );
|
||||
void GDS_ClearExt( struct GDS_Device* Device, bool full, ...);
|
||||
void GDS_Clear( struct GDS_Device* Device, int Color );
|
||||
void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
|
||||
|
||||
#endif
|
||||
24
lib/display/core/gds_default_if.h
Normal file
24
lib/display/core/gds_default_if.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef _GDS_DEFAULT_IF_H_
|
||||
#define _GDS_DEFAULT_IF_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct GDS_Device;
|
||||
|
||||
bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int speed );
|
||||
bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin, int BacklightPin );
|
||||
|
||||
bool GDS_SPIInit( int SPI, int DC );
|
||||
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed, int BacklightPin, int Mode );
|
||||
|
||||
bool GDS_QSPIInit( int QSPI, int DC );
|
||||
bool GDS_QSPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed, int BacklightPin, int Mode );
|
||||
bool GDS_QSPIBusInit( int MOSIPin, int MISOPin, int CLKPin, int Host );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
379
lib/display/core/gds_draw.c
Normal file
379
lib/display/core/gds_draw.c
Normal file
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 Tara Keeling
|
||||
* 2020 Philippe G.
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <esp_attr.h>
|
||||
|
||||
#include "gds_private.h"
|
||||
#include "gds.h"
|
||||
#include "gds_draw.h"
|
||||
|
||||
static const unsigned char BitReverseTable256[] =
|
||||
{
|
||||
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
|
||||
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
|
||||
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
|
||||
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
|
||||
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
|
||||
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
|
||||
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
|
||||
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
|
||||
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
|
||||
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
|
||||
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
|
||||
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
|
||||
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
|
||||
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
|
||||
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
|
||||
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
|
||||
};
|
||||
|
||||
__attribute__( ( always_inline ) ) static inline void SwapInt( int* a, int* b ) {
|
||||
int Temp = *b;
|
||||
|
||||
*b = *a;
|
||||
*a = Temp;
|
||||
}
|
||||
|
||||
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
Device->DrawPixelFast( Device, X, Y, Color );
|
||||
}
|
||||
|
||||
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
DrawPixel( Device, X, Y, Color );
|
||||
}
|
||||
|
||||
void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color ) {
|
||||
int XEnd = x + Width;
|
||||
|
||||
Device->Dirty = true;
|
||||
|
||||
if (x < 0) x = 0;
|
||||
if (XEnd >= Device->Width) XEnd = Device->Width - 1;
|
||||
|
||||
if (y < 0) y = 0;
|
||||
else if (y >= Device->Height) y = Device->Height - 1;
|
||||
|
||||
for ( ; x < XEnd; x++ ) Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) {
|
||||
int YEnd = y + Height;
|
||||
|
||||
Device->Dirty = true;
|
||||
|
||||
if (x < 0) x = 0;
|
||||
if (x >= Device->Width) x = Device->Width - 1;
|
||||
|
||||
if (y < 0) y = 0;
|
||||
else if (YEnd >= Device->Height) YEnd = Device->Height - 1;
|
||||
|
||||
for ( ; y < YEnd; y++ ) DrawPixel( Device, x, y, Color );
|
||||
}
|
||||
|
||||
static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
|
||||
int dx = ( x1 - x0 );
|
||||
int dy = ( y1 - y0 );
|
||||
int Error = 0;
|
||||
int Incr = 1;
|
||||
int x = x0;
|
||||
int y = y0;
|
||||
|
||||
if ( dy < 0 ) {
|
||||
Incr = -1;
|
||||
dy = -dy;
|
||||
}
|
||||
|
||||
Error = ( dy * 2 ) - dx;
|
||||
|
||||
for ( ; x < x1; x++ ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
if ( Error > 0 ) {
|
||||
Error-= ( dx * 2 );
|
||||
y+= Incr;
|
||||
}
|
||||
|
||||
Error+= ( dy * 2 );
|
||||
}
|
||||
}
|
||||
|
||||
static inline void DrawTallLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
|
||||
int dx = ( x1 - x0 );
|
||||
int dy = ( y1 - y0 );
|
||||
int Error = 0;
|
||||
int Incr = 1;
|
||||
int x = x0;
|
||||
int y = y0;
|
||||
|
||||
if ( dx < 0 ) {
|
||||
Incr = -1;
|
||||
dx = -dx;
|
||||
}
|
||||
|
||||
Error = ( dx * 2 ) - dy;
|
||||
|
||||
for ( ; y < y1; y++ ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
if ( Error > 0 ) {
|
||||
Error-= ( dy * 2 );
|
||||
x+= Incr;
|
||||
}
|
||||
|
||||
Error+= ( dx * 2 );
|
||||
}
|
||||
}
|
||||
|
||||
void GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
|
||||
if ( x0 == x1 ) {
|
||||
GDS_DrawVLine( Device, x0, y0, ( y1 - y0 ), Color );
|
||||
} else if ( y0 == y1 ) {
|
||||
GDS_DrawHLine( Device, x0, y0, ( x1 - x0 ), Color );
|
||||
} else {
|
||||
Device->Dirty = true;
|
||||
if ( abs( x1 - x0 ) > abs( y1 - y0 ) ) {
|
||||
/* Wide ( run > rise ) */
|
||||
if ( x0 > x1 ) {
|
||||
SwapInt( &x0, &x1 );
|
||||
SwapInt( &y0, &y1 );
|
||||
}
|
||||
|
||||
DrawWideLine( Device, x0, y0, x1, y1, Color );
|
||||
} else {
|
||||
/* Tall ( rise > run ) */
|
||||
if ( y0 > y1 ) {
|
||||
SwapInt( &y0, &y1 );
|
||||
SwapInt( &x0, &x1 );
|
||||
}
|
||||
|
||||
DrawTallLine( Device, x0, y0, x1, y1, Color );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color, bool Fill ) {
|
||||
int Width = ( x2 - x1 );
|
||||
int Height = ( y2 - y1 );
|
||||
|
||||
Device->Dirty = true;
|
||||
|
||||
if ( Fill == false ) {
|
||||
/* Top side */
|
||||
GDS_DrawHLine( Device, x1, y1, Width, Color );
|
||||
|
||||
/* Bottom side */
|
||||
GDS_DrawHLine( Device, x1, y1 + Height, Width, Color );
|
||||
|
||||
/* Left side */
|
||||
GDS_DrawVLine( Device, x1, y1, Height, Color );
|
||||
|
||||
/* Right side */
|
||||
GDS_DrawVLine( Device, x1 + Width, y1, Height, Color );
|
||||
} else {
|
||||
/* Fill the box by drawing horizontal lines */
|
||||
for ( ; y1 <= y2; y1++ ) {
|
||||
GDS_DrawHLine( Device, x1, y1, Width, Color );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* Process graphic display data from column-oriented data (MSbit first)
|
||||
*/
|
||||
void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ) {
|
||||
if (!Height) Height = Device->Height;
|
||||
if (!Width) Width = Device->Width;
|
||||
|
||||
if (Device->DrawBitmapCBR) {
|
||||
Device->DrawBitmapCBR( Device, Data, Width, Height, Color );
|
||||
} else if (Device->Depth == 1) {
|
||||
|
||||
Height >>= 3;
|
||||
|
||||
// need to do row/col swap and bit-reverse
|
||||
for (int r = 0; r < Height; r++) {
|
||||
uint8_t *optr = Device->Framebuffer + r*Device->Width, *iptr = Data + r;
|
||||
for (int c = Width; --c >= 0;) {
|
||||
*optr++ = BitReverseTable256[*iptr];
|
||||
iptr += Height;
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth == 4) {
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
int LineLen = Device->Width >> 1;
|
||||
|
||||
Height >>= 3;
|
||||
Color &= 0x0f;
|
||||
|
||||
if (Device->HighNibble) {
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
// we need to linearize code to let compiler better optimize
|
||||
if (c & 0x01) {
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
|
||||
} else {
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
|
||||
}
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
|
||||
}
|
||||
} else {
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
// we need to linearize code to let compiler better optimize
|
||||
if (c & 0x01) {
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
|
||||
} else {
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
|
||||
}
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth == 8) {
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
int LineLen = Device->Width;
|
||||
|
||||
Height >>= 3;
|
||||
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
|
||||
// we need to linearize code to let compiler better optimize
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen;
|
||||
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + c; }
|
||||
}
|
||||
} else if (Device->Depth == 16) {
|
||||
uint16_t *optr = (uint16_t*) Device->Framebuffer;
|
||||
int LineLen = Device->Width;
|
||||
|
||||
Height >>= 3;
|
||||
Color = __builtin_bswap16(Color);
|
||||
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
|
||||
// we need to linearize code to let compiler better optimize
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen;
|
||||
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = (uint16_t*) Device->Framebuffer + c; }
|
||||
}
|
||||
} else if (Device->Depth == 24) {
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
int LineLen = Device->Width * 3;
|
||||
|
||||
Height >>= 3;
|
||||
if (Device->Mode == GDS_RGB666) Color = ((Color << 4) & 0xff0000) | ((Color << 2) & 0xff00) | (Color & 0x00ff);
|
||||
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
|
||||
// we need to linearize code to let compiler better optimize
|
||||
#define SET24(O,D) O[0]=(D)>>16; O[1]=(D)>>8; O[2]=(D);
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen;
|
||||
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + c * 3; }
|
||||
}
|
||||
} else {
|
||||
Height >>= 3;
|
||||
|
||||
// don't know bitdepth, use brute-force solution
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = *Data++;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
|
||||
if (++r == Height) { c++; r = 0; }
|
||||
}
|
||||
/* for better understanding, here is the mundane version
|
||||
for (int x = 0; x < Width; x++) {
|
||||
for (int y = 0; y < Height; y++) {
|
||||
uint8_t Byte = *Data++;
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 0, ((Byte >> 7) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 1, ((Byte >> 6) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 2, ((Byte >> 5) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 3, ((Byte >> 4) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 4, ((Byte >> 3) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 5, ((Byte >> 2) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 6, ((Byte >> 1) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 7, ((Byte >> 0) & 0x01) * Color );
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
}
|
||||
33
lib/display/core/gds_draw.h
Normal file
33
lib/display/core/gds_draw.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _GDS_DRAW_H_
|
||||
#define _GDS_DRAW_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_attr.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color );
|
||||
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color );
|
||||
void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color );
|
||||
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color );
|
||||
void GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color );
|
||||
void GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color, bool Fill );
|
||||
// draw a bitmap with source 1-bit depth organized in column and col0 = bit7 of byte 0
|
||||
void GDS_DrawBitmapCBR( struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
39
lib/display/core/gds_err.h
Normal file
39
lib/display/core/gds_err.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef _GDS_ERR_H_
|
||||
#define _GDS_ERR_H_
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define GDS_DoAbort( )
|
||||
|
||||
#if ! defined NullCheck
|
||||
#define NullCheck( ptr, retexpr ) { \
|
||||
if ( ptr == NULL ) { \
|
||||
ESP_LOGE( __FUNCTION__, "%s == NULL", #ptr ); \
|
||||
GDS_DoAbort( ); \
|
||||
retexpr; \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ! defined ESP_ERROR_CHECK_NONFATAL
|
||||
#define ESP_ERROR_CHECK_NONFATAL( expr, retexpr ) { \
|
||||
esp_err_t __err_rc = ( expr ); \
|
||||
if ( __err_rc != ESP_OK ) { \
|
||||
ESP_LOGE( __FUNCTION__, "%s != ESP_OK, result: %d", #expr, __err_rc ); \
|
||||
GDS_DoAbort( ); \
|
||||
retexpr; \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ! defined CheckBounds
|
||||
#define CheckBounds( expr, retexpr ) { \
|
||||
if ( expr ) { \
|
||||
ESP_LOGE( __FUNCTION__, "Line %d: %s", __LINE__, #expr ); \
|
||||
GDS_DoAbort( ); \
|
||||
retexpr; \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
272
lib/display/core/gds_font.c
Normal file
272
lib/display/core/gds_font.c
Normal file
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 Tara Keeling
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include "gds_private.h"
|
||||
#include "gds.h"
|
||||
#include "gds_font.h"
|
||||
#include "gds_draw.h"
|
||||
#include "gds_err.h"
|
||||
|
||||
static int RoundUpFontHeight( const struct GDS_FontDef* Font ) {
|
||||
int Height = Font->Height;
|
||||
|
||||
if ( ( Height % 8 ) != 0 ) {
|
||||
return ( ( Height + 7 ) / 8 ) * 8;
|
||||
}
|
||||
|
||||
return Height;
|
||||
}
|
||||
|
||||
static const uint8_t* GetCharPtr( const struct GDS_FontDef* Font, char Character ) {
|
||||
return &Font->FontData[ ( Character - Font->StartChar ) * ( ( Font->Width * ( RoundUpFontHeight( Font ) / 8 ) ) + 1 ) ];
|
||||
}
|
||||
|
||||
void GDS_FontDrawChar( struct GDS_Device* Device, char Character, int x, int y, int Color ) {
|
||||
const uint8_t* GlyphData = NULL;
|
||||
int GlyphColumnLen = 0;
|
||||
int CharStartX = 0;
|
||||
int CharStartY = 0;
|
||||
int CharWidth = 0;
|
||||
int CharHeight = 0;
|
||||
int CharEndX = 0;
|
||||
int CharEndY = 0;
|
||||
int OffsetX = 0;
|
||||
int OffsetY = 0;
|
||||
int YByte = 0;
|
||||
int YBit = 0;
|
||||
int i = 0;
|
||||
|
||||
NullCheck( ( GlyphData = GetCharPtr( Device->Font, Character ) ), return );
|
||||
|
||||
if ( Character >= Device->Font->StartChar && Character <= Device->Font->EndChar ) {
|
||||
/* The first byte in the glyph data is the width of the character in pixels, skip over */
|
||||
GlyphData++;
|
||||
GlyphColumnLen = RoundUpFontHeight( Device->Font ) / 8;
|
||||
|
||||
CharWidth = GDS_FontGetCharWidth( Device, Character );
|
||||
CharHeight = GDS_FontGetHeight( Device );
|
||||
|
||||
CharStartX = x;
|
||||
CharStartY = y;
|
||||
|
||||
CharEndX = CharStartX + CharWidth;
|
||||
CharEndY = CharStartY + CharHeight;
|
||||
|
||||
/* If the character is partially offscreen offset the end by
|
||||
* distance between (coord) and 0.
|
||||
*/
|
||||
OffsetX = ( CharStartX < 0 ) ? abs( CharStartX ) : 0;
|
||||
OffsetY = ( CharStartY < 0 ) ? abs( CharStartY ) : 0;
|
||||
|
||||
/* This skips into the proper column within the glyph data */
|
||||
GlyphData+= ( OffsetX * GlyphColumnLen );
|
||||
|
||||
CharStartX+= OffsetX;
|
||||
CharStartY+= OffsetY;
|
||||
|
||||
/* Do not attempt to draw if this character is entirely offscreen */
|
||||
if ( CharEndX < 0 || CharStartX >= Device->TextWidth || CharEndY < 0 || CharStartY >= Device->Height ) {
|
||||
ClipDebug( x, y );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Do not attempt to draw past the end of the screen */
|
||||
CharEndX = ( CharEndX >= Device->TextWidth ) ? Device->TextWidth - 1 : CharEndX;
|
||||
CharEndY = ( CharEndY >= Device->Height ) ? Device->Height - 1 : CharEndY;
|
||||
Device->Dirty = true;
|
||||
|
||||
for ( x = CharStartX; x < CharEndX; x++ ) {
|
||||
for ( y = CharStartY, i = 0; y < CharEndY && i < CharHeight; y++, i++ ) {
|
||||
YByte = ( i + OffsetY ) / 8;
|
||||
YBit = ( i + OffsetY ) & 0x07;
|
||||
|
||||
if ( GlyphData[ YByte ] & BIT( YBit ) ) {
|
||||
DrawPixel( Device, x, y, Color );
|
||||
}
|
||||
}
|
||||
|
||||
GlyphData+= GlyphColumnLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const struct GDS_FontDef* GDS_SetFont( struct GDS_Device* Display, const struct GDS_FontDef* Font ) {
|
||||
const struct GDS_FontDef* OldFont = Display->Font;
|
||||
|
||||
Display->FontForceProportional = false;
|
||||
Display->FontForceMonospace = false;
|
||||
Display->Font = Font;
|
||||
|
||||
return OldFont;
|
||||
}
|
||||
|
||||
void GDS_FontForceProportional( struct GDS_Device* Display, bool Force ) {
|
||||
Display->FontForceProportional = Force;
|
||||
}
|
||||
|
||||
void GDS_FontForceMonospace( struct GDS_Device* Display, bool Force ) {
|
||||
Display->FontForceMonospace = Force;
|
||||
}
|
||||
|
||||
int GDS_FontGetWidth( struct GDS_Device* Display ) {
|
||||
return Display->Font->Width;
|
||||
}
|
||||
|
||||
int GDS_FontGetHeight( struct GDS_Device* Display ) {
|
||||
return Display->Font->Height;
|
||||
}
|
||||
|
||||
int GDS_FontGetCharWidth( struct GDS_Device* Display, char Character ) {
|
||||
const uint8_t* CharPtr = NULL;
|
||||
int Width = 0;
|
||||
|
||||
if ( Character >= Display->Font->StartChar && Character <= Display->Font->EndChar ) {
|
||||
CharPtr = GetCharPtr( Display->Font, Character );
|
||||
|
||||
Width = ( Display->Font->Monospace == true ) ? Display->Font->Width : *CharPtr;
|
||||
|
||||
if ( Display->FontForceMonospace == true ) {
|
||||
Width = Display->Font->Width;
|
||||
}
|
||||
|
||||
if ( Display->FontForceProportional == true ) {
|
||||
Width = *CharPtr;
|
||||
}
|
||||
}
|
||||
|
||||
return Width;
|
||||
}
|
||||
|
||||
int GDS_FontGetMaxCharsPerRow( struct GDS_Device* Display ) {
|
||||
return Display->TextWidth / Display->Font->Width;
|
||||
}
|
||||
|
||||
int GDS_FontGetMaxCharsPerColumn( struct GDS_Device* Display ) {
|
||||
return Display->Height / Display->Font->Height;
|
||||
}
|
||||
|
||||
int GDS_FontGetCharHeight( struct GDS_Device* Display ) {
|
||||
return Display->Font->Height;
|
||||
}
|
||||
|
||||
int GDS_FontMeasureString( struct GDS_Device* Display, const char* Text ) {
|
||||
int Width = 0;
|
||||
int Len = 0;
|
||||
|
||||
NullCheck( Text, return 0 );
|
||||
|
||||
for ( Len = strlen( Text ); Len >= 0; Len--, Text++ ) {
|
||||
if ( *Text >= Display->Font->StartChar && *Text <= Display->Font->EndChar ) {
|
||||
Width+= GDS_FontGetCharWidth( Display, *Text );
|
||||
}
|
||||
}
|
||||
|
||||
return Width;
|
||||
}
|
||||
|
||||
void GDS_FontDrawString( struct GDS_Device* Display, int x, int y, const char* Text, int Color ) {
|
||||
int Len = 0;
|
||||
int i = 0;
|
||||
|
||||
NullCheck( Text, return );
|
||||
|
||||
for ( Len = strlen( Text ), i = 0; i < Len; i++ ) {
|
||||
GDS_FontDrawChar( Display, *Text, x, y, Color );
|
||||
|
||||
x+= GDS_FontGetCharWidth( Display, *Text );
|
||||
Text++;
|
||||
}
|
||||
}
|
||||
|
||||
void GDS_FontDrawAnchoredString( struct GDS_Device* Display, TextAnchor Anchor, const char* Text, int Color ) {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
NullCheck( Text, return );
|
||||
|
||||
GDS_FontGetAnchoredStringCoords( Display, &x, &y, Anchor, Text );
|
||||
GDS_FontDrawString( Display, x, y, Text, Color );
|
||||
}
|
||||
|
||||
void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int* OutY, TextAnchor Anchor, const char* Text ) {
|
||||
int StringWidth = 0;
|
||||
int StringHeight = 0;
|
||||
|
||||
NullCheck( OutX, return );
|
||||
NullCheck( OutY, return );
|
||||
NullCheck( Text, return );
|
||||
|
||||
StringWidth = GDS_FontMeasureString( Display, Text );
|
||||
StringHeight = GDS_FontGetCharHeight( Display );
|
||||
|
||||
switch ( Anchor ) {
|
||||
case TextAnchor_East: {
|
||||
*OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
|
||||
*OutX = ( Display->TextWidth - StringWidth );
|
||||
|
||||
break;
|
||||
}
|
||||
case TextAnchor_West: {
|
||||
*OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
|
||||
*OutX = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
case TextAnchor_North: {
|
||||
*OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
|
||||
*OutY = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
case TextAnchor_South: {
|
||||
*OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
|
||||
*OutY = ( Display->Height - StringHeight );
|
||||
|
||||
break;
|
||||
}
|
||||
case TextAnchor_NorthEast: {
|
||||
*OutX = ( Display->TextWidth - StringWidth );
|
||||
*OutY = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
case TextAnchor_NorthWest: {
|
||||
*OutY = 0;
|
||||
*OutX = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
case TextAnchor_SouthEast: {
|
||||
*OutY = ( Display->Height - StringHeight );
|
||||
*OutX = ( Display->TextWidth - StringWidth );
|
||||
|
||||
break;
|
||||
}
|
||||
case TextAnchor_SouthWest: {
|
||||
*OutY = ( Display->Height - StringHeight );
|
||||
*OutX = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
case TextAnchor_Center: {
|
||||
*OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
|
||||
*OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
*OutX = 128;
|
||||
*OutY = 64;
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
92
lib/display/core/gds_font.h
Normal file
92
lib/display/core/gds_font.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#ifndef _GDS_FONT_H_
|
||||
#define _GDS_FONT_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct GDS_Device;
|
||||
|
||||
/*
|
||||
* X-GLCD Font format:
|
||||
*
|
||||
* First byte of glyph is it's width in pixels.
|
||||
* Each data byte represents 8 pixels going down from top to bottom.
|
||||
*
|
||||
* Example glyph layout for a 16x16 font
|
||||
* 'a': [Glyph width][Pixel column 0][Pixel column 1] where the number of pixel columns is the font height divided by 8
|
||||
* 'b': [Glyph width][Pixel column 0][Pixel column 1]...
|
||||
* 'c': And so on...
|
||||
*/
|
||||
|
||||
struct GDS_FontDef {
|
||||
const uint8_t* FontData;
|
||||
|
||||
int Width;
|
||||
int Height;
|
||||
|
||||
int StartChar;
|
||||
int EndChar;
|
||||
|
||||
bool Monospace;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
TextAnchor_East = 0,
|
||||
TextAnchor_West,
|
||||
TextAnchor_North,
|
||||
TextAnchor_South,
|
||||
TextAnchor_NorthEast,
|
||||
TextAnchor_NorthWest,
|
||||
TextAnchor_SouthEast,
|
||||
TextAnchor_SouthWest,
|
||||
TextAnchor_Center
|
||||
} TextAnchor;
|
||||
|
||||
const struct GDS_FontDef* GDS_SetFont( struct GDS_Device* Display, const struct GDS_FontDef* Font );
|
||||
|
||||
void GDS_FontForceProportional( struct GDS_Device* Display, bool Force );
|
||||
void GDS_FontForceMonospace( struct GDS_Device* Display, bool Force );
|
||||
|
||||
int GDS_FontGetWidth( struct GDS_Device* Display );
|
||||
int GDS_FontGetHeight( struct GDS_Device* Display );
|
||||
|
||||
int GDS_FontGetMaxCharsPerRow( struct GDS_Device* Display );
|
||||
int GDS_FontGetMaxCharsPerColumn( struct GDS_Device* Display );
|
||||
|
||||
int GDS_FontGetCharWidth( struct GDS_Device* Display, char Character );
|
||||
int GDS_FontGetCharHeight( struct GDS_Device* Display );
|
||||
int GDS_FontMeasureString( struct GDS_Device* Display, const char* Text );
|
||||
int GDS_FontMeasureStringLine( struct GDS_Device* Display, int Line, const char* Text );
|
||||
|
||||
void GDS_FontDrawChar( struct GDS_Device* Display, char Character, int x, int y, int Color );
|
||||
void GDS_FontDrawString( struct GDS_Device* Display, int x, int y, const char* Text, int Color );
|
||||
void GDS_FontDrawAnchoredString( struct GDS_Device* Display, TextAnchor Anchor, const char* Text, int Color );
|
||||
void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int* OutY, TextAnchor Anchor, const char* Text );
|
||||
|
||||
extern const struct GDS_FontDef Font_droid_sans_fallback_11x13;
|
||||
extern const struct GDS_FontDef Font_droid_sans_fallback_15x17;
|
||||
extern const struct GDS_FontDef Font_droid_sans_fallback_24x28;
|
||||
|
||||
extern const struct GDS_FontDef Font_droid_sans_mono_7x13;
|
||||
extern const struct GDS_FontDef Font_droid_sans_mono_13x24;
|
||||
extern const struct GDS_FontDef Font_droid_sans_mono_16x31;
|
||||
|
||||
extern const struct GDS_FontDef Font_liberation_mono_9x15;
|
||||
extern const struct GDS_FontDef Font_liberation_mono_13x21;
|
||||
extern const struct GDS_FontDef Font_liberation_mono_17x30;
|
||||
|
||||
extern const struct GDS_FontDef Font_Tarable7Seg_16x32;
|
||||
extern const struct GDS_FontDef Font_Tarable7Seg_32x64;
|
||||
|
||||
extern const struct GDS_FontDef Font_line_1;
|
||||
extern const struct GDS_FontDef Font_line_2;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
442
lib/display/core/gds_image.c
Normal file
442
lib/display/core/gds_image.c
Normal file
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "math.h"
|
||||
#ifdef TJPGD_ROM
|
||||
#include "esp32/rom/tjpgd.h"
|
||||
#else
|
||||
#include "tjpgd.h"
|
||||
#endif
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "gds.h"
|
||||
#include "gds_private.h"
|
||||
#include "gds_image.h"
|
||||
|
||||
const static char TAG[] = "ImageDec";
|
||||
|
||||
#define SCRATCH_SIZE 3100
|
||||
|
||||
//Data that is passed from the decoder function to the infunc/outfunc functions.
|
||||
typedef struct {
|
||||
const unsigned char *InData; // Pointer to jpeg data
|
||||
int InPos; // Current position in jpeg data
|
||||
int Width, Height;
|
||||
uint8_t Mode;
|
||||
union {
|
||||
void *OutData;
|
||||
struct { // DirectDraw
|
||||
struct GDS_Device *Device;
|
||||
int XOfs, YOfs;
|
||||
int XMin, YMin;
|
||||
int Depth;
|
||||
};
|
||||
};
|
||||
} JpegCtx;
|
||||
|
||||
/****************************************************************************************
|
||||
* RGB conversion (24 bits 888: RRRRRRRRGGGGGGGGBBBBBBBB and 16 bits 565: RRRRRGGGGGGBBBBB = B31..B0)
|
||||
* so in other words for an array of 888 bytes: [0]=B, [1]=G, [2]=R, ...
|
||||
* monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b)
|
||||
* grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) )
|
||||
*/
|
||||
static inline int Scaler332(uint8_t *Pixels) {
|
||||
return (Pixels[2] & ~0x1f) | ((Pixels[1] & ~0x1f) >> 3) | (Pixels[0] >> 6);
|
||||
}
|
||||
|
||||
static inline int Scaler444(uint8_t *Pixels) {
|
||||
return ((Pixels[2] & ~0x0f) << 4) | (Pixels[1] & ~0x0f) | (Pixels[0] >> 4);
|
||||
}
|
||||
|
||||
static inline int Scaler555(uint8_t *Pixels) {
|
||||
return ((Pixels[2] & ~0x07) << 7) | ((Pixels[1] & ~0x07) << 2) | (Pixels[0] >> 3);
|
||||
}
|
||||
|
||||
static inline int Scaler565(uint8_t *Pixels) {
|
||||
return ((Pixels[2] & ~0x07) << 8) | ((Pixels[1] & ~0x03) << 3) | (Pixels[0] >> 3);
|
||||
}
|
||||
|
||||
static inline int Scaler666(uint8_t *Pixels) {
|
||||
return ((Pixels[2] & ~0x03) << 10) | ((Pixels[1] & ~0x03) << 4) | (Pixels[0] >> 2);
|
||||
}
|
||||
|
||||
static inline int Scaler888(uint8_t *Pixels) {
|
||||
return (Pixels[2] << 16) | (Pixels[1] << 8) | Pixels[0];
|
||||
}
|
||||
|
||||
static inline int ScalerGray(uint8_t *Pixels) {
|
||||
return (Pixels[2] * 14 + Pixels[1] * 76 + Pixels[0] * 38) >> 7;
|
||||
}
|
||||
|
||||
static unsigned InHandler(JDEC *Decoder, uint8_t *Buf, unsigned Len) {
|
||||
JpegCtx *Context = (JpegCtx*) Decoder->device;
|
||||
if (Buf) memcpy(Buf, Context->InData + Context->InPos, Len);
|
||||
Context->InPos += Len;
|
||||
return Len;
|
||||
}
|
||||
|
||||
#define OUTHANDLER(F) \
|
||||
for (int y = Frame->top; y <= Frame->bottom; y++) { \
|
||||
for (int x = Frame->left; x <= Frame->right; x++) { \
|
||||
OutData[Context->Width * y + x] = F(Pixels); \
|
||||
Pixels += 3; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define OUTHANDLER24(F) \
|
||||
for (int y = Frame->top; y <= Frame->bottom; y++) { \
|
||||
uint8_t *p = OutData + (Context->Width * y + Frame->left) * 3; \
|
||||
for (int c = Frame->right - Frame->left; c-- >= 0;) { \
|
||||
uint32_t v = F(Pixels); \
|
||||
*p++ = v; *p++ = v >> 8; *p++ = v >> 16; \
|
||||
Pixels += 3; \
|
||||
} \
|
||||
}
|
||||
|
||||
static unsigned OutHandler(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
|
||||
JpegCtx *Context = (JpegCtx*) Decoder->device;
|
||||
uint8_t *Pixels = (uint8_t*) Bitmap;
|
||||
|
||||
// decoded image is RGB888
|
||||
if (Context->Mode == GDS_RGB888) {
|
||||
uint8_t *OutData = (uint8_t*) Context->OutData;
|
||||
OUTHANDLER24(Scaler888);
|
||||
} else if (Context->Mode == GDS_RGB666) {
|
||||
uint8_t *OutData = (uint8_t*) Context->OutData;
|
||||
OUTHANDLER24(Scaler666);
|
||||
} else if (Context->Mode == GDS_RGB565) {
|
||||
uint16_t *OutData = (uint16_t*) Context->OutData;
|
||||
OUTHANDLER(Scaler565);
|
||||
} else if (Context->Mode == GDS_RGB555) {
|
||||
uint16_t *OutData = (uint16_t*) Context->OutData;
|
||||
OUTHANDLER(Scaler555);
|
||||
} else if (Context->Mode == GDS_RGB444) {
|
||||
uint16_t *OutData = (uint16_t*) Context->OutData;
|
||||
OUTHANDLER(Scaler444);
|
||||
} else if (Context->Mode == GDS_RGB332) {
|
||||
uint8_t *OutData = (uint8_t*) Context->OutData;
|
||||
OUTHANDLER(Scaler332);
|
||||
} else if (Context->Mode <= GDS_GRAYSCALE) {
|
||||
uint8_t *OutData = (uint8_t*) Context->OutData;
|
||||
OUTHANDLER(ScalerGray);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Convert the RGB888 to destination color plane, use DrawPixel and not "fast"
|
||||
// version as X,Y may be beyond screen
|
||||
#define OUTHANDLERDIRECT(F,S) \
|
||||
for (int y = Frame->top; y <= Frame->bottom; y++) { \
|
||||
if (y < Context->YMin) continue; \
|
||||
for (int x = Frame->left; x <= Frame->right; x++) { \
|
||||
if (x < Context->XMin) continue; \
|
||||
DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, F(Pixels) >> S); \
|
||||
Pixels += 3; \
|
||||
} \
|
||||
}
|
||||
|
||||
static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
|
||||
JpegCtx *Context = (JpegCtx*) Decoder->device;
|
||||
uint8_t *Pixels = (uint8_t*) Bitmap;
|
||||
int Shift = 8 - Context->Depth;
|
||||
|
||||
// decoded image is RGB888, shift only make sense for grayscale
|
||||
if (Context->Mode == GDS_RGB888) {
|
||||
OUTHANDLERDIRECT(Scaler888, 0);
|
||||
} else if (Context->Mode == GDS_RGB666) {
|
||||
OUTHANDLERDIRECT(Scaler666, 0);
|
||||
} else if (Context->Mode == GDS_RGB565) {
|
||||
OUTHANDLERDIRECT(Scaler565, 0);
|
||||
} else if (Context->Mode == GDS_RGB555) {
|
||||
OUTHANDLERDIRECT(Scaler555, 0);
|
||||
} else if (Context->Mode == GDS_RGB444) {
|
||||
OUTHANDLERDIRECT(Scaler444, 0);
|
||||
} else if (Context->Mode == GDS_RGB332) {
|
||||
OUTHANDLERDIRECT(Scaler332, 0);
|
||||
} else if (Context->Mode <= GDS_GRAYSCALE) {
|
||||
OUTHANDLERDIRECT(ScalerGray, Shift);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
//Decode the embedded image into pixel lines that can be used with the rest of the logic.
|
||||
static void* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly, int RGB_Mode) {
|
||||
JDEC Decoder;
|
||||
JpegCtx Context;
|
||||
char *Scratch = malloc(SCRATCH_SIZE);
|
||||
|
||||
if (!Scratch) {
|
||||
ESP_LOGE(TAG, "Cannot allocate workspace");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Context.OutData = NULL;
|
||||
Context.InData = Source;
|
||||
Context.InPos = 0;
|
||||
|
||||
//Prepare and decode the jpeg.
|
||||
int Res = jd_prepare(&Decoder, InHandler, Scratch, SCRATCH_SIZE, (void*) &Context);
|
||||
if (Width) *Width = Decoder.width;
|
||||
if (Height) *Height = Decoder.height;
|
||||
Decoder.scale = Scale;
|
||||
|
||||
if (Res == JDR_OK && !SizeOnly) {
|
||||
if (RGB_Mode <= GDS_RGB332) Context.OutData = malloc(Decoder.width * Decoder.height);
|
||||
else if (RGB_Mode < GDS_RGB666) Context.OutData = malloc(Decoder.width * Decoder.height * 2);
|
||||
else if (RGB_Mode <= GDS_RGB888) Context.OutData = malloc(Decoder.width * Decoder.height * 3);
|
||||
|
||||
// find the scaling factor
|
||||
uint8_t N = 0, ScaleInt = ceil(1.0 / Scale);
|
||||
ScaleInt--; ScaleInt |= ScaleInt >> 1; ScaleInt |= ScaleInt >> 2; ScaleInt++;
|
||||
while (ScaleInt >>= 1) N++;
|
||||
if (N > 3) {
|
||||
ESP_LOGW(TAG, "Image will not fit %dx%d", Decoder.width, Decoder.height);
|
||||
N = 3;
|
||||
}
|
||||
|
||||
// ready to decode
|
||||
if (Context.OutData) {
|
||||
Context.Width = Decoder.width / (1 << N);
|
||||
Context.Height = Decoder.height / (1 << N);
|
||||
Context.Mode = RGB_Mode;
|
||||
if (Width) *Width = Context.Width;
|
||||
if (Height) *Height = Context.Height;
|
||||
Res = jd_decomp(&Decoder, OutHandler, N);
|
||||
if (Res != JDR_OK) {
|
||||
ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Can't allocate bitmap %dx%d or invalid mode %d", Decoder.width, Decoder.height, RGB_Mode);
|
||||
}
|
||||
} else if (!SizeOnly) {
|
||||
ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", Res);
|
||||
}
|
||||
|
||||
// free scratch area
|
||||
if (Scratch) free(Scratch);
|
||||
return Context.OutData;
|
||||
}
|
||||
|
||||
void* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, int RGB_Mode) {
|
||||
return DecodeJPEG(Source, Width, Height, Scale, false, RGB_Mode);
|
||||
}
|
||||
|
||||
void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height) {
|
||||
DecodeJPEG(Source, Width, Height, 1, true, -1);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* RGB conversion (24 bits: RRRRRRRRGGGGGGGGBBBBBBBB and 16 bits 565: RRRRRGGGGGGBBBBB = B31..B0)
|
||||
* so in other words for an array of 888 bytes: [0]=B, [1]=G, [2]=R, ...
|
||||
* monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b)
|
||||
* grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) )
|
||||
*/
|
||||
|
||||
static inline int ToGray888(uint8_t **Pixel) {
|
||||
uint32_t v = *(*Pixel)++; v |= *(*Pixel)++ << 8; v |= *(*Pixel)++ << 16;
|
||||
return (((v & 0xff) * 14) + ((v >> 8) & 0xff) * 76 + ((v >> 16) * 38) + 1) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToGray666(uint8_t **Pixel) {
|
||||
uint32_t v = *(*Pixel)++; v |= *(*Pixel)++ << 8; v |= *(*Pixel)++ << 16;
|
||||
return (((v & 0x3f) * 14) + ((v >> 6) & 0x3f) * 76 + ((v >> 12) * 38) + 1) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToGray565(uint16_t **Pixel) {
|
||||
uint16_t v = *(*Pixel)++;
|
||||
return ((((v & 0x1f) * 14) << 1) + ((v >> 5) & 0x3f) * 76 + (((v >> 11) * 38) << 1) + 1) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToGray555(uint16_t **Pixel) {
|
||||
uint16_t v = *(*Pixel)++;
|
||||
return ((v & 0x1f) * 14 + ((v >> 5) & 0x1f) * 76 + (v >> 10) * 38) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToGray444(uint16_t **Pixel) {
|
||||
uint16_t v = *(*Pixel)++;
|
||||
return ((v & 0x0f) * 14 + ((v >> 4) & 0x0f) * 76 + (v >> 8) * 38) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToGray332(uint8_t **Pixel) {
|
||||
uint8_t v = *(*Pixel)++;
|
||||
return ((((v & 0x3) * 14) << 1) + ((v >> 2) & 0x7) * 76 + (v >> 5) * 38 + 1) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToSelf(uint8_t **Pixel) {
|
||||
return *(*Pixel)++;
|
||||
}
|
||||
|
||||
#define DRAW_GRAYRGB(S,F) \
|
||||
if (Scale > 0) { \
|
||||
for (int r = 0; r < Height; r++) { \
|
||||
for (int c = 0; c < Width; c++) { \
|
||||
DrawPixel( Device, c + x, r + y, F(S) >> Scale); \
|
||||
} \
|
||||
} \
|
||||
} else { \
|
||||
for (int r = 0; r < Height; r++) { \
|
||||
for (int c = 0; c < Width; c++) { \
|
||||
DrawPixel( Device, c + x, r + y, F(S) << -Scale); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define DRAW_RGB(T) \
|
||||
T *S = (T*) Image; \
|
||||
for (int r = 0; r < Height; r++) { \
|
||||
for (int c = 0; c < Width; c++) { \
|
||||
DrawPixel(Device, c + x, r + y, *S++); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define DRAW_RGB24 \
|
||||
uint8_t *S = (uint8_t*) Image; \
|
||||
for (int r = 0; r < Height; r++) { \
|
||||
for (int c = 0; c < Width; c++) { \
|
||||
uint32_t v = *S++; v |= *S++ << 8; v |= *S++ << 16; \
|
||||
DrawPixel(Device, c + x, r + y, v); \
|
||||
} \
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Decode the embedded image into pixel lines that can be used with the rest of the logic.
|
||||
*/
|
||||
void GDS_DrawRGB( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) {
|
||||
|
||||
// don't do anything if driver supplies a draw function
|
||||
if (Device->DrawRGB) {
|
||||
Device->DrawRGB( Device, Image, x, y, Width, Height, RGB_Mode );
|
||||
Device->Dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// RGB type displays
|
||||
if (Device->Mode > GDS_GRAYSCALE) {
|
||||
// image must match the display mode!
|
||||
if (Device->Mode != RGB_Mode) {
|
||||
ESP_LOGE(TAG, "non-matching display & image mode %u %u", Device->Mode, RGB_Mode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (RGB_Mode == GDS_RGB332) {
|
||||
DRAW_RGB(uint8_t);
|
||||
} else if (RGB_Mode < GDS_RGB666) {
|
||||
DRAW_RGB(uint16_t);
|
||||
} else {
|
||||
DRAW_RGB24;
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// set the right scaler when displaying grayscale
|
||||
if (RGB_Mode <= GDS_GRAYSCALE) {
|
||||
int Scale = 8 - Device->Depth;
|
||||
DRAW_GRAYRGB(&Image,ToSelf);
|
||||
} else if (RGB_Mode == GDS_RGB332) {
|
||||
int Scale = 3 - Device->Depth;
|
||||
DRAW_GRAYRGB(&Image,ToGray332);
|
||||
} else if (RGB_Mode < GDS_RGB666) {
|
||||
if (RGB_Mode == GDS_RGB565) {
|
||||
int Scale = 6 - Device->Depth;
|
||||
DRAW_GRAYRGB((uint16_t**)&Image,ToGray565);
|
||||
} else if (RGB_Mode == GDS_RGB555) {
|
||||
int Scale = 5 - Device->Depth;
|
||||
DRAW_GRAYRGB((uint16_t**)&Image,ToGray555);
|
||||
} else if (RGB_Mode == GDS_RGB444) {
|
||||
int Scale = 4 - Device->Depth;
|
||||
DRAW_GRAYRGB((uint16_t**)&Image,ToGray444)
|
||||
}
|
||||
} else {
|
||||
if (RGB_Mode == GDS_RGB666) {
|
||||
int Scale = 6 - Device->Depth;
|
||||
DRAW_GRAYRGB(&Image,ToGray666);
|
||||
} else if (RGB_Mode == GDS_RGB888) {
|
||||
int Scale = 8 - Device->Depth;
|
||||
DRAW_GRAYRGB(&Image,ToGray888);
|
||||
}
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Decode the embedded image into pixel lines that can be used with the rest of the logic.
|
||||
*/
|
||||
bool GDS_DrawJPEG(struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit) {
|
||||
JDEC Decoder;
|
||||
JpegCtx Context;
|
||||
bool Ret = false;
|
||||
char *Scratch = malloc(SCRATCH_SIZE);
|
||||
|
||||
if (!Scratch) {
|
||||
ESP_LOGE(TAG, "Cannot allocate workspace");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Populate fields of the JpegCtx struct.
|
||||
Context.InData = Source;
|
||||
Context.InPos = 0;
|
||||
Context.XOfs = x;
|
||||
Context.YOfs = y;
|
||||
Context.Device = Device;
|
||||
Context.Depth = Device->Depth;
|
||||
|
||||
//Prepare and decode the jpeg.
|
||||
int Res = jd_prepare(&Decoder, InHandler, Scratch, SCRATCH_SIZE, (void*) &Context);
|
||||
Context.Width = Decoder.width;
|
||||
Context.Height = Decoder.height;
|
||||
|
||||
if (Res == JDR_OK) {
|
||||
uint8_t N = 0;
|
||||
|
||||
// do we need to fit the image
|
||||
if (Fit & GDS_IMAGE_FIT) {
|
||||
float XRatio = (Device->Width - x) / (float) Decoder.width, YRatio = (Device->Height - y) / (float) Decoder.height;
|
||||
uint8_t Ratio = XRatio < YRatio ? ceil(1/XRatio) : ceil(1/YRatio);
|
||||
Ratio--; Ratio |= Ratio >> 1; Ratio |= Ratio >> 2; Ratio++;
|
||||
while (Ratio >>= 1) N++;
|
||||
if (N > 3) {
|
||||
ESP_LOGW(TAG, "Image will not fit %dx%d", Decoder.width, Decoder.height);
|
||||
N = 3;
|
||||
}
|
||||
Context.Width /= 1 << N;
|
||||
Context.Height /= 1 << N;
|
||||
}
|
||||
|
||||
// then place it
|
||||
if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = (Device->Width + x - Context.Width) / 2;
|
||||
else if (Fit & GDS_IMAGE_RIGHT) Context.XOfs = Device->Width - Context.Width;
|
||||
if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = (Device->Height + y - Context.Height) / 2;
|
||||
else if (Fit & GDS_IMAGE_BOTTOM) Context.YOfs = Device->Height - Context.Height;
|
||||
|
||||
Context.XMin = x - Context.XOfs;
|
||||
Context.YMin = y - Context.YOfs;
|
||||
Context.Mode = Device->Mode;
|
||||
|
||||
// do decompress & draw
|
||||
Res = jd_decomp(&Decoder, OutHandlerDirect, N);
|
||||
if (Res == JDR_OK) {
|
||||
Device->Dirty = true;
|
||||
Ret = true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", Res);
|
||||
}
|
||||
|
||||
// free scratch area
|
||||
if (Scratch) free(Scratch);
|
||||
return Ret;
|
||||
}
|
||||
|
||||
32
lib/display/core/gds_image.h
Normal file
32
lib/display/core/gds_image.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
// no progressive JPEG handling
|
||||
|
||||
struct GDS_Device;
|
||||
|
||||
// Fit options for GDS_DrawJPEG
|
||||
#define GDS_IMAGE_LEFT 0x00
|
||||
#define GDS_IMAGE_CENTER_X 0x01
|
||||
#define GDS_IMAGE_RIGHT 0x04
|
||||
#define GDS_IMAGE_TOP 0x00
|
||||
#define GDS_IMAGE_BOTTOM 0x08
|
||||
#define GDS_IMAGE_CENTER_Y 0x02
|
||||
#define GDS_IMAGE_CENTER (GDS_IMAGE_CENTER_X | GDS_IMAGE_CENTER_Y)
|
||||
#define GDS_IMAGE_FIT 0x10 // re-scale by a factor of 2^N (up to 3)
|
||||
|
||||
// Width and Height can be NULL if you already know them (actual scaling is closest ^2)
|
||||
void* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, int RGB_Mode); // can be 8, 16 or 24 bits per pixel in return
|
||||
void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height);
|
||||
bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit);
|
||||
void GDS_DrawRGB( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
|
||||
165
lib/display/core/gds_private.h
Normal file
165
lib/display/core/gds_private.h
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _GDS_PRIVATE_H_
|
||||
#define _GDS_PRIVATE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_attr.h"
|
||||
#include "gds.h"
|
||||
#include "gds_err.h"
|
||||
|
||||
#define GDS_ALLOC_NONE 0x80
|
||||
#define GDS_ALLOC_IRAM 0x01
|
||||
#define GDS_ALLOC_IRAM_SPI 0x02
|
||||
|
||||
#define GDS_CLIPDEBUG_NONE 0
|
||||
#define GDS_CLIPDEBUG_WARNING 1
|
||||
#define GDS_CLIPDEBUG_ERROR 2
|
||||
|
||||
#if CONFIG_GDS_CLIPDEBUG == GDS_CLIPDEBUG_NONE
|
||||
/*
|
||||
* Clip silently with no console output.
|
||||
*/
|
||||
#define ClipDebug( x, y )
|
||||
#elif CONFIG_GDS_CLIPDEBUG == GDS_CLIPDEBUG_WARNING
|
||||
/*
|
||||
* Log clipping to the console as a warning.
|
||||
*/
|
||||
#define ClipDebug( x, y ) { \
|
||||
ESP_LOGW( __FUNCTION__, "Line %d: Pixel at %d, %d CLIPPED", __LINE__, x, y ); \
|
||||
}
|
||||
#elif CONFIG_GDS_CLIPDEBUG == GDS_CLIPDEBUG_ERROR
|
||||
/*
|
||||
* Log clipping as an error to the console.
|
||||
* Also invokes an abort with stack trace.
|
||||
*/
|
||||
#define ClipDebug( x, y ) { \
|
||||
ESP_LOGE( __FUNCTION__, "Line %d: Pixel at %d, %d CLIPPED, ABORT", __LINE__, x, y ); \
|
||||
abort( ); \
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#define GDS_ALWAYS_INLINE __attribute__( ( always_inline ) )
|
||||
|
||||
#define MAX_LINES 8
|
||||
|
||||
#if ! defined BIT
|
||||
#define BIT( n ) ( 1 << ( n ) )
|
||||
#endif
|
||||
|
||||
struct GDS_Device;
|
||||
struct GDS_FontDef;
|
||||
|
||||
/*
|
||||
* These can optionally return a succeed/fail but are as of yet unused in the driver.
|
||||
*/
|
||||
typedef bool ( *WriteCommandProc ) ( struct GDS_Device* Device, uint8_t Command );
|
||||
typedef bool ( *WriteDataProc ) ( struct GDS_Device* Device, const uint8_t* Data, size_t DataLength );
|
||||
|
||||
struct spi_device_t;
|
||||
typedef struct spi_device_t* spi_device_handle_t;
|
||||
|
||||
#define GDS_IF_SPI 0
|
||||
#define GDS_IF_I2C 1
|
||||
|
||||
struct GDS_Device {
|
||||
uint8_t IF;
|
||||
int8_t RSTPin;
|
||||
struct {
|
||||
int8_t Pin, Channel;
|
||||
int PWM;
|
||||
} Backlight;
|
||||
union {
|
||||
// I2C Specific
|
||||
struct {
|
||||
uint8_t Address;
|
||||
};
|
||||
// SPI specific
|
||||
struct {
|
||||
spi_device_handle_t SPIHandle;
|
||||
int8_t CSPin;
|
||||
};
|
||||
};
|
||||
|
||||
// cooked text mode
|
||||
struct {
|
||||
int16_t Y, Space;
|
||||
const struct GDS_FontDef* Font;
|
||||
} Lines[MAX_LINES];
|
||||
|
||||
uint16_t Width, TextWidth;
|
||||
uint16_t Height;
|
||||
uint8_t Depth, Mode;
|
||||
bool HighNibble;
|
||||
|
||||
uint8_t Alloc;
|
||||
uint8_t* Framebuffer;
|
||||
uint32_t FramebufferSize;
|
||||
bool Dirty;
|
||||
|
||||
// default fonts when using direct draw
|
||||
const struct GDS_FontDef* Font;
|
||||
bool FontForceProportional;
|
||||
bool FontForceMonospace;
|
||||
|
||||
// various driver-specific method
|
||||
// must always provide
|
||||
bool (*Init)( struct GDS_Device* Device);
|
||||
void (*Update)( struct GDS_Device* Device );
|
||||
// may provide if supported
|
||||
void (*SetContrast)( struct GDS_Device* Device, uint8_t Contrast );
|
||||
void (*DisplayOn)( struct GDS_Device* Device );
|
||||
void (*DisplayOff)( struct GDS_Device* Device );
|
||||
void (*SetLayout)( struct GDS_Device* Device, struct GDS_Layout *Layout );
|
||||
// must provide for depth other than 1 (vertical) and 4 (may provide for optimization)
|
||||
void (*DrawPixelFast)( struct GDS_Device* Device, int X, int Y, int Color );
|
||||
void (*DrawBitmapCBR)(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color );
|
||||
// may provide for optimization
|
||||
void (*DrawRGB)( struct GDS_Device* Device, uint8_t *Image,int x, int y, int Width, int Height, int RGB_Mode );
|
||||
void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
|
||||
// may provide for tweaking
|
||||
void (*SPIParams)(int Speed, uint8_t *mode, uint16_t *CS_pre, uint8_t *CS_post);
|
||||
|
||||
// interface-specific methods
|
||||
WriteCommandProc WriteCommand;
|
||||
WriteDataProc WriteData;
|
||||
|
||||
// 32 bytes for whatever the driver wants (should be aligned as it's 32 bits)
|
||||
uint32_t Private[8];
|
||||
};
|
||||
|
||||
bool GDS_Reset( struct GDS_Device* Device );
|
||||
bool GDS_Init( struct GDS_Device* Device );
|
||||
|
||||
static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
|
||||
bool Result = (
|
||||
( x >= 0 ) &&
|
||||
( x < Device->Width ) &&
|
||||
( y >= 0 ) &&
|
||||
( y < Device->Height )
|
||||
) ? true : false;
|
||||
|
||||
#if CONFIG_GDS_CLIPDEBUG > 0
|
||||
if ( Result == false ) {
|
||||
ClipDebug( x, y );
|
||||
}
|
||||
#endif
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
static inline void IRAM_ATTR DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
207
lib/display/core/gds_text.c
Normal file
207
lib/display/core/gds_text.c
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "gds_private.h"
|
||||
#include "gds.h"
|
||||
#include "gds_draw.h"
|
||||
#include "gds_text.h"
|
||||
|
||||
#define max(a,b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
static char TAG[] = "gds";
|
||||
|
||||
/****************************************************************************************
|
||||
* Set fonts for each line in text mode
|
||||
*/
|
||||
static const struct GDS_FontDef *GuessFont( struct GDS_Device *Device, int FontType) {
|
||||
switch(FontType) {
|
||||
case GDS_FONT_DEFAULT:
|
||||
return Device->Font;
|
||||
case GDS_FONT_LINE_1:
|
||||
return &Font_line_1;
|
||||
case GDS_FONT_LINE_2:
|
||||
return &Font_line_2;
|
||||
case GDS_FONT_MEDIUM:
|
||||
//return &Font_droid_sans_fallback_15x17;
|
||||
case GDS_FONT_SMALL:
|
||||
default:
|
||||
return &Font_droid_sans_fallback_11x13;
|
||||
#ifdef USE_LARGE_FONTS
|
||||
case GDS_FONT_LARGE:
|
||||
return &Font_droid_sans_fallback_24x28;
|
||||
case GDS_FONT_SEGMENT:
|
||||
if (Device->Height == 32) return &Font_Tarable7Seg_16x32;
|
||||
else return &Font_Tarable7Seg_32x64;
|
||||
#else
|
||||
case GDS_FONT_LARGE:
|
||||
case GDS_FONT_SEGMENT:
|
||||
ESP_LOGW(TAG, "large fonts disabled");
|
||||
//return &Font_droid_sans_fallback_15x17;
|
||||
return &Font_droid_sans_fallback_11x13;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* Set fonts for each line in text mode
|
||||
*/
|
||||
bool GDS_TextSetFontAuto(struct GDS_Device* Device, int N, int FontType, int Space) {
|
||||
const struct GDS_FontDef *Font = GuessFont( Device, FontType );
|
||||
return GDS_TextSetFont( Device, N, Font, Space );
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Set fonts for each line in text mode
|
||||
*/
|
||||
bool GDS_TextSetFont(struct GDS_Device* Device, int N, const struct GDS_FontDef *Font, int Space) {
|
||||
if (--N >= MAX_LINES) return false;
|
||||
|
||||
Device->Lines[N].Font = Font;
|
||||
|
||||
// re-calculate lines absolute position
|
||||
Device->Lines[N].Space = Space;
|
||||
Device->Lines[0].Y = Device->Lines[0].Space;
|
||||
for (int i = 1; i <= N; i++) Device->Lines[i].Y = Device->Lines[i-1].Y + Device->Lines[i-1].Font->Height + Device->Lines[i].Space;
|
||||
|
||||
ESP_LOGI(TAG, "Adding line %u at %d (height:%u)", N + 1, Device->Lines[N].Y, Device->Lines[N].Font->Height);
|
||||
|
||||
if (Device->Lines[N].Y + Device->Lines[N].Font->Height > Device->Height) {
|
||||
ESP_LOGW(TAG, "line does not fit display");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Text) {
|
||||
int Width, X = Pos;
|
||||
|
||||
// counting 1..n
|
||||
N--;
|
||||
|
||||
GDS_SetFont( Device, Device->Lines[N].Font );
|
||||
if (Attr & GDS_TEXT_MONOSPACE) GDS_FontForceMonospace( Device, true );
|
||||
|
||||
Width = GDS_FontMeasureString( Device, Text );
|
||||
|
||||
// adjusting position, erase only EoL for rigth-justified
|
||||
if (Pos == GDS_TEXT_RIGHT) X = Device->TextWidth - Width - 1;
|
||||
else if (Pos == GDS_TEXT_CENTER) X = (Device->TextWidth - Width) / 2;
|
||||
|
||||
// erase if requested
|
||||
if (Attr & GDS_TEXT_CLEAR) {
|
||||
int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height);
|
||||
for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->TextWidth; c++)
|
||||
for (int y = Y_min; y < Y_max; y++)
|
||||
Device->DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
|
||||
}
|
||||
|
||||
GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE );
|
||||
|
||||
ESP_LOGD(TAG, "displaying %s line %u (x:%d, attr:%u)", Text, N+1, X, Attr);
|
||||
|
||||
// update whole display if requested
|
||||
Device->Dirty = true;
|
||||
if (Attr & GDS_TEXT_UPDATE) GDS_Update( Device );
|
||||
|
||||
return Width + X < Device->TextWidth;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
int GDS_GetTextWidth(struct GDS_Device* Device, int N, int Attr, char *Text) {
|
||||
const struct GDS_FontDef *Font = GDS_SetFont( Device, Device->Lines[N-1].Font );
|
||||
|
||||
if (Attr & GDS_TEXT_MONOSPACE) GDS_FontForceMonospace( Device, true );
|
||||
int Width = GDS_FontMeasureString( Device, Text );
|
||||
GDS_SetFont( Device, Font );
|
||||
|
||||
return Width;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Try to align string for better scrolling visual. there is probably much better to do
|
||||
*/
|
||||
int GDS_TextStretch(struct GDS_Device* Device, int N, char *String, int Max) {
|
||||
char Space[] = " ";
|
||||
int Len = strlen(String), Extra = 0, Boundary;
|
||||
|
||||
N--;
|
||||
|
||||
// we might already fit
|
||||
GDS_SetFont( Device, Device->Lines[N].Font );
|
||||
if (GDS_FontMeasureString( Device, String ) <= Device->TextWidth) return 0;
|
||||
|
||||
// add some space for better visual
|
||||
strncat(String, Space, Max-Len);
|
||||
String[Max] = '\0';
|
||||
Len = strlen(String);
|
||||
|
||||
// mark the end of the extended string
|
||||
Boundary = GDS_FontMeasureString( Device, String );
|
||||
|
||||
// add a full display width
|
||||
while (Len < Max && GDS_FontMeasureString( Device, String ) - Boundary < Device->TextWidth) {
|
||||
String[Len++] = String[Extra++];
|
||||
String[Len] = '\0';
|
||||
}
|
||||
|
||||
return Boundary;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void GDS_TextPos(struct GDS_Device* Device, int FontType, int Where, int Attr, char *Text, ...) {
|
||||
va_list args;
|
||||
|
||||
TextAnchor Anchor = TextAnchor_Center;
|
||||
|
||||
if (Attr & GDS_TEXT_CLEAR) GDS_Clear( Device, GDS_COLOR_BLACK );
|
||||
|
||||
if (!Text) return;
|
||||
|
||||
va_start(args, Text);
|
||||
|
||||
switch(Where) {
|
||||
case GDS_TEXT_TOP_LEFT:
|
||||
default:
|
||||
Anchor = TextAnchor_NorthWest;
|
||||
break;
|
||||
case GDS_TEXT_MIDDLE_LEFT:
|
||||
Anchor = TextAnchor_West;
|
||||
break;
|
||||
case GDS_TEXT_BOTTOM_LEFT:
|
||||
Anchor = TextAnchor_SouthWest;
|
||||
break;
|
||||
case GDS_TEXT_CENTERED:
|
||||
Anchor = TextAnchor_Center;
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Displaying %s at %u with attribute %u", Text, Anchor, Attr);
|
||||
|
||||
GDS_SetFont( Device, GuessFont( Device, FontType ) );
|
||||
GDS_FontDrawAnchoredString( Device, Anchor, Text, GDS_COLOR_WHITE );
|
||||
|
||||
Device->Dirty = true;
|
||||
if (Attr & GDS_TEXT_UPDATE) GDS_Update( Device );
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
36
lib/display/core/gds_text.h
Normal file
36
lib/display/core/gds_text.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gds_font.h"
|
||||
|
||||
#define GDS_TEXT_CLEAR 0x01
|
||||
#define GDS_TEXT_CLEAR_EOL 0x02
|
||||
#define GDS_TEXT_UPDATE 0x04
|
||||
#define GDS_TEXT_MONOSPACE 0x08
|
||||
|
||||
// these ones are for 'Pos' parameter of TextLine
|
||||
#define GDS_TEXT_LEFT 0
|
||||
#define GDS_TEXT_RIGHT 0xff00
|
||||
#define GDS_TEXT_CENTER 0xff01
|
||||
|
||||
// these ones are for the 'Where' parameter of TextPos
|
||||
enum { GDS_TEXT_TOP_LEFT, GDS_TEXT_MIDDLE_LEFT, GDS_TEXT_BOTTOM_LEFT, GDS_TEXT_CENTERED };
|
||||
|
||||
enum { GDS_FONT_DEFAULT, GDS_FONT_LINE_1, GDS_FONT_LINE_2, GDS_FONT_SEGMENT,
|
||||
GDS_FONT_TINY, GDS_FONT_SMALL, GDS_FONT_MEDIUM, GDS_FONT_LARGE, GDS_FONT_FONT_HUGE };
|
||||
|
||||
struct GDS_Device;
|
||||
|
||||
bool GDS_TextSetFontAuto(struct GDS_Device* Device, int N, int FontType, int Space);
|
||||
bool GDS_TextSetFont(struct GDS_Device* Device, int N, const struct GDS_FontDef *Font, int Space);
|
||||
bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Text);
|
||||
int GDS_GetTextWidth(struct GDS_Device* Device, int N, int Attr, char *Text);
|
||||
int GDS_TextStretch(struct GDS_Device* Device, int N, char *String, int Max);
|
||||
void GDS_TextPos(struct GDS_Device* Device, int FontType, int Where, int Attr, char *Text, ...);
|
||||
128
lib/display/core/ifaces/default_if_i2c.c
Normal file
128
lib/display/core/ifaces/default_if_i2c.c
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 Tara Keeling
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <driver/i2c.h>
|
||||
#include <driver/gpio.h>
|
||||
#include "gds.h"
|
||||
#include "gds_err.h"
|
||||
#include "gds_private.h"
|
||||
#include "gds_default_if.h"
|
||||
|
||||
static int I2CPortNumber;
|
||||
static int I2CWait;
|
||||
|
||||
static const int GDS_I2C_COMMAND_MODE = 0x80;
|
||||
static const int GDS_I2C_DATA_MODE = 0x40;
|
||||
|
||||
static bool I2CDefaultWriteBytes( int Address, bool IsCommand, const uint8_t* Data, size_t DataLength );
|
||||
static bool I2CDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command );
|
||||
static bool I2CDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data, size_t DataLength );
|
||||
|
||||
/*
|
||||
* Initializes the i2c master with the parameters specified
|
||||
* in the component configuration in sdkconfig.h.
|
||||
*
|
||||
* Returns true on successful init of the i2c bus.
|
||||
*/
|
||||
bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
|
||||
I2CPortNumber = PortNumber;
|
||||
|
||||
I2CWait = pdMS_TO_TICKS( Speed ? (250 * 250000) / Speed : 250 );
|
||||
|
||||
if (SDA != -1 && SCL != -1) {
|
||||
i2c_config_t Config = { 0 };
|
||||
|
||||
Config.mode = I2C_MODE_MASTER;
|
||||
Config.sda_io_num = SDA;
|
||||
Config.sda_pullup_en = GPIO_PULLUP_ENABLE;
|
||||
Config.scl_io_num = SCL;
|
||||
Config.scl_pullup_en = GPIO_PULLUP_ENABLE;
|
||||
Config.master.clk_speed = Speed ? Speed : 400000;
|
||||
|
||||
ESP_ERROR_CHECK_NONFATAL( i2c_param_config( I2CPortNumber, &Config ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( i2c_driver_install( I2CPortNumber, Config.mode, 0, 0, 0 ), return false );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attaches a display to the I2C bus using default communication functions.
|
||||
*
|
||||
* Params:
|
||||
* Device: Pointer to your GDS_Device object
|
||||
* Width: Width of display
|
||||
* Height: Height of display
|
||||
* I2CAddress: Address of your display
|
||||
* RSTPin: Optional GPIO pin to use for hardware reset, if none pass -1 for this parameter.
|
||||
*
|
||||
* Returns true on successful init of display.
|
||||
*/
|
||||
bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin, int BacklightPin ) {
|
||||
NullCheck( Device, return false );
|
||||
|
||||
Device->WriteCommand = I2CDefaultWriteCommand;
|
||||
Device->WriteData = I2CDefaultWriteData;
|
||||
Device->Address = I2CAddress;
|
||||
Device->RSTPin = RSTPin;
|
||||
Device->Backlight.Pin = BacklightPin;
|
||||
Device->IF = GDS_IF_I2C;
|
||||
Device->Width = Device->TextWidth = Width;
|
||||
Device->Height = Height;
|
||||
|
||||
if ( RSTPin >= 0 ) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( RSTPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( RSTPin, 1 ), return false );
|
||||
GDS_Reset( Device );
|
||||
}
|
||||
|
||||
return GDS_Init( Device );
|
||||
}
|
||||
|
||||
static bool I2CDefaultWriteBytes( int Address, bool IsCommand, const uint8_t* Data, size_t DataLength ) {
|
||||
i2c_cmd_handle_t* CommandHandle = NULL;
|
||||
static uint8_t ModeByte = 0;
|
||||
|
||||
NullCheck( Data, return false );
|
||||
|
||||
if ( ( CommandHandle = i2c_cmd_link_create( ) ) != NULL ) {
|
||||
ModeByte = ( IsCommand == true ) ? GDS_I2C_COMMAND_MODE: GDS_I2C_DATA_MODE;
|
||||
|
||||
ESP_ERROR_CHECK_NONFATAL( i2c_master_start( CommandHandle ), goto error );
|
||||
ESP_ERROR_CHECK_NONFATAL( i2c_master_write_byte( CommandHandle, ( Address << 1 ) | I2C_MASTER_WRITE, true ), goto error );
|
||||
ESP_ERROR_CHECK_NONFATAL( i2c_master_write_byte( CommandHandle, ModeByte, true ), goto error );
|
||||
ESP_ERROR_CHECK_NONFATAL( i2c_master_write( CommandHandle, ( uint8_t* ) Data, DataLength, true ), goto error );
|
||||
ESP_ERROR_CHECK_NONFATAL( i2c_master_stop( CommandHandle ), goto error );
|
||||
|
||||
ESP_ERROR_CHECK_NONFATAL( i2c_master_cmd_begin( I2CPortNumber, CommandHandle, I2CWait ), goto error );
|
||||
i2c_cmd_link_delete( CommandHandle );
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (CommandHandle) i2c_cmd_link_delete( CommandHandle );
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool I2CDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command ) {
|
||||
uint8_t CommandByte = ( uint8_t ) Command;
|
||||
|
||||
NullCheck( Device, return false );
|
||||
return I2CDefaultWriteBytes( Device->Address, true, ( const uint8_t* ) &CommandByte, 1 );
|
||||
}
|
||||
|
||||
static bool I2CDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data, size_t DataLength ) {
|
||||
NullCheck( Device, return false );
|
||||
NullCheck( Data, return false );
|
||||
|
||||
return I2CDefaultWriteBytes( Device->Address, false, Data, DataLength );
|
||||
}
|
||||
161
lib/display/core/ifaces/default_if_qspi.c
Normal file
161
lib/display/core/ifaces/default_if_qspi.c
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* QSPI Interface for Guition JC4827W543C ILI9488 Display
|
||||
* Supports ESP32-S3 QSPI peripheral
|
||||
*
|
||||
* (c) Guition Support 2026
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <driver/spi_master.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <freertos/task.h>
|
||||
#include "gds.h"
|
||||
#include "gds_err.h"
|
||||
#include "gds_private.h"
|
||||
#include "gds_default_if.h"
|
||||
|
||||
static const int GDS_QSPI_Command_Mode = 0;
|
||||
static const int GDS_QSPI_Data_Mode = 1;
|
||||
|
||||
static spi_host_device_t QSPIHost;
|
||||
static int DCPin;
|
||||
|
||||
static bool QSPIDefaultWriteBytes( spi_device_handle_t QSPIHandle, int WriteMode, const uint8_t* Data, size_t DataLength );
|
||||
static bool QSPIDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command );
|
||||
static bool QSPIDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data, size_t DataLength );
|
||||
|
||||
bool GDS_QSPIInit( int QSPI, int DC ) {
|
||||
QSPIHost = QSPI;
|
||||
DCPin = DC;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GDS_QSPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int BackLightPin, int Speed, int Mode ) {
|
||||
spi_device_interface_config_t QSPIDeviceConfig = { };
|
||||
spi_device_handle_t QSPIDevice;
|
||||
|
||||
NullCheck( Device, return false );
|
||||
|
||||
if (CSPin >= 0) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( CSPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( CSPin, 0 ), return false );
|
||||
}
|
||||
|
||||
QSPIDeviceConfig.clock_speed_hz = Speed > 0 ? Speed : SPI_MASTER_FREQ_20M;
|
||||
QSPIDeviceConfig.spics_io_num = CSPin;
|
||||
QSPIDeviceConfig.queue_size = 4;
|
||||
QSPIDeviceConfig.mode = Mode;
|
||||
QSPIDeviceConfig.flags = SPI_DEVICE_HALFDUPLEX;
|
||||
QSPIDeviceConfig.duty_cycle_pos = 128; // 50% duty cycle
|
||||
|
||||
// QSPI specific configuration for ESP32-S3
|
||||
QSPIDeviceConfig.command_bits = 8;
|
||||
QSPIDeviceConfig.address_bits = 24;
|
||||
QSPIDeviceConfig.address_len = 3;
|
||||
|
||||
if( spi_bus_add_device( QSPIHost, &QSPIDeviceConfig, &QSPIDevice ) != ESP_OK ) {
|
||||
GDS_LOG_ERROR( "Failed to add QSPI device to host" );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( DCPin >= 0 ) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( DCPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( DCPin, 1 ), return false );
|
||||
}
|
||||
|
||||
if( RSTPin >= 0 ) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( RSTPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( RSTPin, 1 ), return false );
|
||||
}
|
||||
|
||||
if( BackLightPin >= 0 ) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( BackLightPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( BackLightPin, 1 ), return false );
|
||||
}
|
||||
|
||||
Device->WriteCommand = QSPIDefaultWriteCommand;
|
||||
Device->WriteData = QSPIDefaultWriteData;
|
||||
Device->DeviceHandle = QSPIDevice;
|
||||
|
||||
// Hardware reset if pin is available
|
||||
if( RSTPin >= 0 ) {
|
||||
gpio_set_level( RSTPin, 0 );
|
||||
vTaskDelay( pdMS_TO_TICKS( 10 ) );
|
||||
gpio_set_level( RSTPin, 1 );
|
||||
vTaskDelay( pdMS_TO_TICKS( 120 ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool QSPIDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command ) {
|
||||
return QSPIDefaultWriteBytes( Device->DeviceHandle, GDS_QSPI_Command_Mode, &Command, 1 );
|
||||
}
|
||||
|
||||
static bool QSPIDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data, size_t DataLength ) {
|
||||
if( DCPin >= 0 ) {
|
||||
gpio_set_level( DCPin, 1 );
|
||||
}
|
||||
|
||||
bool Result = QSPIDefaultWriteBytes( Device->DeviceHandle, GDS_QSPI_Data_Mode, Data, DataLength );
|
||||
|
||||
if( DCPin >= 0 ) {
|
||||
gpio_set_level( DCPin, 0 );
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
static bool QSPIDefaultWriteBytes( spi_device_handle_t QSPIHandle, int WriteMode, const uint8_t* Data, size_t DataLength ) {
|
||||
spi_transaction_ext_t SPITransaction = { };
|
||||
SPITransaction.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY;
|
||||
SPITransaction.base.cmd = 0;
|
||||
SPITransaction.base.addr = 0;
|
||||
SPITransaction.base.length = DataLength * 8;
|
||||
SPITransaction.base.tx_buffer = Data;
|
||||
SPITransaction.base.rx_buffer = NULL;
|
||||
SPITransaction.command_bits = 8;
|
||||
SPITransaction.address_bits = 0;
|
||||
SPITransaction.dummy_bits = 0;
|
||||
|
||||
if( WriteMode == GDS_QSPI_Command_Mode ) {
|
||||
SPITransaction.command_bits = 8;
|
||||
SPITransaction.base.cmd = Data[0];
|
||||
SPITransaction.base.length = 0;
|
||||
SPITransaction.base.tx_buffer = NULL;
|
||||
}
|
||||
|
||||
if( spi_device_transmit( QSPIHandle, (spi_transaction_t*) &SPITransaction ) != ESP_OK ) {
|
||||
GDS_LOG_ERROR( "QSPI transaction failed" );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GDS_QSPIBusInit( int MOSIPin, int MISOPin, int CLKPin, int Host ) {
|
||||
spi_bus_config_t BusConfig = { };
|
||||
|
||||
NullCheck( Host >= SPI2_HOST && Host <= SPI3_HOST, return false );
|
||||
|
||||
// QSPI pin configuration for ESP32-S3
|
||||
BusConfig.mosi_io_num = MOSIPin;
|
||||
BusConfig.miso_io_num = MISOPin;
|
||||
BusConfig.sclk_io_num = CLKPin;
|
||||
BusConfig.quadwp_io_num = -1; // Not used for ILI9488
|
||||
BusConfig.quadhd_io_num = -1; // Not used for ILI9488
|
||||
BusConfig.max_transfer_sz = 65536;
|
||||
BusConfig.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_NATIVE_PINS;
|
||||
|
||||
if( spi_bus_initialize( Host, &BusConfig, DMA_CH_AUTO ) != ESP_OK ) {
|
||||
GDS_LOG_ERROR( "Failed to initialize QSPI bus" );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
119
lib/display/core/ifaces/default_if_spi.c
Normal file
119
lib/display/core/ifaces/default_if_spi.c
Normal file
@@ -0,0 +1,119 @@
|
||||
|
||||
/**
|
||||
* Copyright (c) 2017-2018 Tara Keeling
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <driver/spi_master.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <freertos/task.h>
|
||||
#include "gds.h"
|
||||
#include "gds_err.h"
|
||||
#include "gds_private.h"
|
||||
#include "gds_default_if.h"
|
||||
|
||||
static const int GDS_SPI_Command_Mode = 0;
|
||||
static const int GDS_SPI_Data_Mode = 1;
|
||||
|
||||
static spi_host_device_t SPIHost;
|
||||
static int DCPin;
|
||||
|
||||
static bool SPIDefaultWriteBytes( spi_device_handle_t SPIHandle, int WriteMode, const uint8_t* Data, size_t DataLength );
|
||||
static bool SPIDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command );
|
||||
static bool SPIDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data, size_t DataLength );
|
||||
|
||||
bool GDS_SPIInit( int SPI, int DC ) {
|
||||
SPIHost = SPI;
|
||||
DCPin = DC;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int BackLightPin, int Speed, int Mode ) {
|
||||
spi_device_interface_config_t SPIDeviceConfig = { };
|
||||
spi_device_handle_t SPIDevice;
|
||||
|
||||
NullCheck( Device, return false );
|
||||
|
||||
if (CSPin >= 0) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( CSPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( CSPin, 0 ), return false );
|
||||
}
|
||||
|
||||
SPIDeviceConfig.clock_speed_hz = Speed > 0 ? Speed : SPI_MASTER_FREQ_8M;
|
||||
SPIDeviceConfig.spics_io_num = CSPin;
|
||||
SPIDeviceConfig.queue_size = 1;
|
||||
SPIDeviceConfig.mode = Mode;
|
||||
SPIDeviceConfig.flags = SPI_DEVICE_NO_DUMMY;
|
||||
if (Device->SPIParams) Device->SPIParams(SPIDeviceConfig.clock_speed_hz, &SPIDeviceConfig.mode,
|
||||
&SPIDeviceConfig.cs_ena_pretrans, &SPIDeviceConfig.cs_ena_posttrans);
|
||||
|
||||
ESP_ERROR_CHECK_NONFATAL( spi_bus_add_device( SPIHost, &SPIDeviceConfig, &SPIDevice ), return false );
|
||||
|
||||
Device->WriteCommand = SPIDefaultWriteCommand;
|
||||
Device->WriteData = SPIDefaultWriteData;
|
||||
Device->SPIHandle = SPIDevice;
|
||||
Device->RSTPin = RSTPin;
|
||||
Device->CSPin = CSPin;
|
||||
Device->Backlight.Pin = BackLightPin;
|
||||
Device->IF = GDS_IF_SPI;
|
||||
Device->Width = Device->TextWidth = Width;
|
||||
Device->Height = Height;
|
||||
|
||||
if ( RSTPin >= 0 ) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( RSTPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( RSTPin, 0 ), return false );
|
||||
GDS_Reset( Device );
|
||||
}
|
||||
|
||||
return GDS_Init( Device );
|
||||
}
|
||||
|
||||
static bool SPIDefaultWriteBytes( spi_device_handle_t SPIHandle, int WriteMode, const uint8_t* Data, size_t DataLength ) {
|
||||
spi_transaction_t SPITransaction = { };
|
||||
|
||||
NullCheck( SPIHandle, return false );
|
||||
NullCheck( Data, return false );
|
||||
|
||||
if ( DataLength > 0 ) {
|
||||
gpio_set_level( DCPin, WriteMode );
|
||||
|
||||
SPITransaction.length = DataLength * 8;
|
||||
|
||||
if (DataLength <= 4) {
|
||||
SPITransaction.flags = SPI_TRANS_USE_TXDATA;
|
||||
SPITransaction.tx_data[0] = *Data++; SPITransaction.tx_data[1] = *Data++;
|
||||
SPITransaction.tx_data[2] = *Data++; SPITransaction.tx_data[3] = *Data;
|
||||
} else {
|
||||
SPITransaction.tx_buffer = Data;
|
||||
}
|
||||
|
||||
// only do polling as we don't have contention on SPI (otherwise DMA for transfers > 16 bytes)
|
||||
ESP_ERROR_CHECK_NONFATAL( spi_device_polling_transmit(SPIHandle, &SPITransaction), return false );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SPIDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command ) {
|
||||
static uint8_t CommandByte = 0;
|
||||
|
||||
NullCheck( Device, return false );
|
||||
NullCheck( Device->SPIHandle, return false );
|
||||
|
||||
CommandByte = Command;
|
||||
|
||||
return SPIDefaultWriteBytes( Device->SPIHandle, GDS_SPI_Command_Mode, &CommandByte, 1 );
|
||||
}
|
||||
|
||||
static bool SPIDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data, size_t DataLength ) {
|
||||
NullCheck( Device, return false );
|
||||
NullCheck( Device->SPIHandle, return false );
|
||||
|
||||
return SPIDefaultWriteBytes( Device->SPIHandle, GDS_SPI_Data_Mode, Data, DataLength );
|
||||
}
|
||||
Reference in New Issue
Block a user