Complete display + add JPEG

This commit is contained in:
philippe44
2020-02-26 23:27:28 -08:00
parent a9f1240965
commit 1ae8f80e53
10 changed files with 290 additions and 12 deletions

View File

@@ -165,6 +165,25 @@ static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, i
}
}
static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
uint8_t _Color = Color == GDS_COLOR_BLACK ? 0: 0xff;
uint8_t Width = Device->Width >> 3;
uint8_t *optr = Device->Framebuffer;
for (int r = y1; r <= y2; r++) {
int c = x1;
// for a row that is not on a boundary, not column opt can be done, so handle all columns on that line
while (c & 0x07 && c <= x2) DrawPixel1Fast( Device, c++, r, Color );
// at this point we are aligned on column boundary
int chunk = (x2 - c + 1) >> 3;
memset(optr + Width * r + (c >> 3), _Color, chunk );
c += chunk * 8;
while (c <= x2) DrawPixel1Fast( Device, c++, r, Color );
}
Device->Dirty = true;
}
static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ) {
uint8_t *optr = Device->Framebuffer;
@@ -312,6 +331,7 @@ struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
Device->Update = Update1;
Device->DrawPixelFast = DrawPixel1Fast;
Device->DrawBitmapCBR = DrawBitmapCBR;
Device->ClearWindow = ClearWindow;
} else {
Device->Depth = 4;
}

View File

@@ -69,21 +69,22 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
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;
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++) GDS_DrawPixelFast( Device, c, r, Color);
for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
r++;
}
// go fast if we have more than 8 lines to write
if (r + 8 < y2) {
memset(Device->Framebuffer + Width * r / 8 + x1, _Color, x2 - x1 + 1);
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++) GDS_DrawPixelFast( Device, c, r, Color);
for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
r++;
}
}
@@ -95,12 +96,13 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
} else {
uint8_t _Color = Color | (Color << 4);
uint8_t 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) GDS_DrawPixelFast( Device, c++, r, Color);
int chunk = (x2 - c + 1) >> 1;
memset(Device->Framebuffer + ((r * Width + c) >> 1), _Color, chunk);
memset(optr + ((r * Width + c) >> 1), _Color, chunk);
if (c + chunk <= x2) GDS_DrawPixelFast( Device, x2, r, Color);
}
}

View File

@@ -7,7 +7,7 @@ extern "C" {
struct GDS_Device;
bool GDS_I2CInit( int PortNumber, int SDA, int SCL );
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 );
bool GDS_SPIInit( int SPI, int DC );

View File

@@ -0,0 +1,225 @@
#include <string.h>
#include "math.h"
#include "esp32/rom/tjpgd.h"
#include "esp_log.h"
#include "gds.h"
#include "gds_private.h"
#include "gds_image.h"
const 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;
union {
uint16_t *OutData; // Decompress
struct { // DirectDraw
struct GDS_Device * Device;
int XOfs, YOfs;
int Depth;
};
};
} JpegCtx;
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;
}
static unsigned OutHandler(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
JpegCtx *Context = (JpegCtx*) Decoder->device;
uint8_t *Pixels = (uint8_t*) Bitmap;
for (int y = Frame->top; y <= Frame->bottom; y++) {
if (y < Context->YOfs) continue;
for (int x = Frame->left; x <= Frame->right; x++) {
if (x < Context->XOfs) continue;
// Convert the 888 to RGB565
uint16_t Value = (*Pixels++ & ~0x07) << 8;
Value |= (*Pixels++ & ~0x03) << 3;
Value |= *Pixels++ >> 3;
Context->OutData[Context->Width * y + x] = Value;
}
}
return 1;
}
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;
for (int y = Frame->top; y <= Frame->bottom; y++) {
for (int x = Frame->left; x <= Frame->right; x++) {
// Convert the 888 to RGB565
int Value = ((Pixels[0]*11 + Pixels[1]*59 + Pixels[2]*30) / 100) >> Shift;
Pixels += 3;
// used DrawPixel and not "fast" version as X,Y may be beyond screen
GDS_DrawPixel( Context->Device, Context->XOfs + x, Context->YOfs + y, Value);
}
}
return 1;
}
//Decode the embedded image into pixel lines that can be used with the rest of the logic.
static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly) {
JDEC Decoder;
JpegCtx Context;
char *Scratch = calloc(SCRATCH_SIZE, 1);
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) {
// ready to decode
Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t));
uint8_t N = 0, iScale = 1.0 / Scale;
while (iScale >>= 1) N++;
if (Context.OutData) {
Context.Width = Decoder.width / (1 << N);
Context.Height = Decoder.height / (1 << N);
if (Width) *Width = Context.Width;
if (Height) *Height = Context.Height;
Res = jd_decomp(&Decoder, OutHandler, N > 3 ? 3 : 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", Decoder.width, Decoder.height);
}
} else if (!SizeOnly) {
ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", Res);
}
// free scratch area
if (Scratch) free(Scratch);
return Context.OutData;
}
uint16_t* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale) {
return DecodeJPEG(Source, Width, Height, Scale, false);
}
void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height) {
DecodeJPEG(Source, Width, Height, 1, true);
}
/****************************************************************************************
* Simply draw a RGB565 image
* monoschrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b)
* grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) )
*/
void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) {
if (Device->DrawRGB16) {
Device->DrawRGB16( Device, x, y, Width, Height, RGB_Mode, Image );
} else {
int Scale = Device->Depth < 5 ? 5 - Device->Depth : 0;
switch(RGB_Mode) {
case GDS_RGB565:
for (int c = 0; c < Width; c++) {
for (int r = 0; r < Height; r++) {
int pixel = Image[Width*r + c];
pixel = ((pixel & 0x1f) * 11 + ((((pixel >> 5) & 0x3f) * 59) >> 1) + (pixel >> 11) * 30) / 100;
GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
}
}
break;
case GDS_RGB555:
for (int c = 0; c < Width; c++) {
for (int r = 0; r < Height; r++) {
int pixel = Image[Width*r + c];
pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100;
GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
}
}
break;
case GDS_RGB444:
for (int c = 0; c < Width; c++) {
for (int r = 0; r < Height; r++) {
int pixel = Image[Width*r + c];
pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30;
GDS_DrawPixel( Device, c + x, r + y, pixel >> (Scale - 1));
}
}
break;
}
}
}
//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 = calloc(SCRATCH_SIZE, 1);
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++;
Context.Width /= 1 << N;
Context.Height /= 1 << N;
}
// then place it
if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = x + ((Device->Width - x) - Context.Width) / 2;
if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = y + ((Device->Height - y) - Context.Height) / 2;
// do decompress & draw
Res = jd_decomp(&Decoder, OutHandlerDirect, N > 3 ? 3 : N);
if (Res == JDR_OK) {
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;
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include <stdint.h>
#include "esp_err.h"
// no progressive JPEG handling
struct GDS_Device;
enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 };
#define GDS_IMAGE_TOP 0x00
#define GDS_IMAGE_CENTER_X 0x01
#define GDS_IMAGE_CENTER_Y 0x02
#define GDS_IMAGE_CENTER (GDS_IMAGE_CENTER_X | GDS_IMAGE_CENTER_Y)
#define GDS_IMAGE_FIT 0x10
// Width and Height can be NULL if you already know them (actual scaling is closest ^2)
uint16_t* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale);
void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height);
void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
// set DisplayWidth and DisplayHeight to non-zero if you want autoscale to closest factor ^2 from 0..3
bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit);

View File

@@ -17,6 +17,7 @@
#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;
@@ -31,9 +32,11 @@ static bool I2CDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data,
*
* Returns true on successful init of the i2c bus.
*/
bool GDS_I2CInit( int PortNumber, int SDA, int SCL ) {
bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
I2CPortNumber = PortNumber;
I2CWait = pdMS_TO_TICKS( Speed ? Speed / 4000 : 100 );
if (SDA != -1 && SCL != -1) {
i2c_config_t Config = { 0 };
@@ -42,7 +45,7 @@ bool GDS_I2CInit( int PortNumber, int SDA, int SCL ) {
Config.sda_pullup_en = GPIO_PULLUP_ENABLE;
Config.scl_io_num = SCL;
Config.scl_pullup_en = GPIO_PULLUP_ENABLE;
Config.master.clk_speed = 250000;
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 );
@@ -98,7 +101,7 @@ static bool I2CDefaultWriteBytes( int Address, bool IsCommand, const uint8_t* Da
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, pdMS_TO_TICKS( 1000 ) ), goto error );
ESP_ERROR_CHECK_NONFATAL( i2c_master_cmd_begin( I2CPortNumber, CommandHandle, I2CWait ), goto error );
i2c_cmd_link_delete( CommandHandle );
}

View File

@@ -89,11 +89,12 @@ void display_init(char *welcome) {
// Detect driver interface
if (strstr(config, "I2C") && i2c_system_port != -1) {
int address = 0x3C;
int speed = 0;
if ((p = strcasestr(config, "address")) != NULL) address = atoi(strchr(p, '=') + 1);
init = true;
GDS_I2CInit( i2c_system_port, -1, -1 ) ;
GDS_I2CInit( i2c_system_port, -1, -1, i2c_system_speed ) ;
GDS_I2CAttachDevice( display, width, height, address, -1 );
ESP_LOGI(TAG, "Display is I2C on port %u", address);