Add support for GIF images

This commit is contained in:
L-diy
2023-11-14 23:25:41 +01:00
parent e6255c4fea
commit ad2a630400
5 changed files with 1493 additions and 8 deletions

224
project/Core/Inc/gifdec.h Normal file
View File

@@ -0,0 +1,224 @@
// Copyright 2020 BitBank Software, Inc. All Rights Reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===========================================================================
#ifndef __ANIMATEDGIF__
#define __ANIMATEDGIF__
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#define memcpy_P memcpy
#define PROGMEM
//
// GIF Animator
// Written by Larry Bank
// Copyright (c) 2020 BitBank Software, Inc.
// bitbank@pobox.com
//
// Designed to decode images up to 480x320
// using less than 22K of RAM
//
/* GIF Defines and variables */
#define MAX_CHUNK_SIZE 255
//
// These 2 macros can be changed to limit the amount of RAM
// required by the decoder. For example, decoding 1-bit images to
// a 128x32 display will not need a max code size of 12 nor a palette
// with 256 entries
//
#define MAX_CODE_SIZE 12
#define MAX_COLORS 256
#define LZW_BUF_SIZE (6*MAX_CHUNK_SIZE)
#define LZW_HIGHWATER (4*MAX_CHUNK_SIZE)
#ifdef __LINUX__
#define MAX_WIDTH 2048
#else
#define MAX_WIDTH 320
#endif // __LINUX__
// This buffer is used to store the pixel sequence in reverse order
// it needs to be large enough to hold the longest possible
// sequence (1<<MAX_CODE_SIZE)
#define FILE_BUF_SIZE (1<<MAX_CODE_SIZE)
#define PIXEL_FIRST 0
#define PIXEL_LAST (1<<MAX_CODE_SIZE)
#define LINK_UNUSED 5911 // 0x1717 to use memset
#define LINK_END 5912
#define MAX_HASH 5003
enum {
GIF_PALETTE_RGB565_LE = 0, // little endian (default)
GIF_PALETTE_RGB565_BE, // big endian
GIF_PALETTE_RGB888 // original 24-bpp entries
};
// for compatibility with older code
#define LITTLE_ENDIAN_PIXELS GIF_PALETTE_RGB565_LE
#define BIG_ENDIAN_PIXELS GIF_PALETTE_RGB565_BE
//
// Draw callback pixel type
// RAW = 8-bit palettized pixels requiring transparent pixel handling
// COOKED = 16 or 24-bpp fully rendered pixels ready for display
//
enum {
GIF_DRAW_RAW = 0,
GIF_DRAW_COOKED
};
enum {
GIF_SUCCESS = 0,
GIF_DECODE_ERROR,
GIF_TOO_WIDE,
GIF_INVALID_PARAMETER,
GIF_UNSUPPORTED_FEATURE,
GIF_FILE_NOT_OPEN,
GIF_EARLY_EOF,
GIF_EMPTY_FRAME,
GIF_BAD_FILE,
GIF_ERROR_MEMORY
};
typedef struct gif_file_tag
{
int32_t iPos; // current file position
int32_t iSize; // file size
uint8_t *pData; // memory file pointer
void * fHandle; // class pointer to File/SdFat or whatever you want
} GIFFILE;
typedef struct gif_info_tag
{
int32_t iFrameCount; // total frames in file
int32_t iDuration; // duration of animation in milliseconds
int32_t iMaxDelay; // maximum frame delay
int32_t iMinDelay; // minimum frame delay
} GIFINFO;
typedef struct gif_draw_tag
{
int iX, iY; // Corner offset of this frame on the canvas
int y; // current line being drawn (0 = top line of image)
int iWidth, iHeight; // size of this frame
void *pUser; // user supplied pointer
uint8_t *pPixels; // 8-bit source pixels for this line
uint16_t *pPalette; // little or big-endian RGB565 palette entries (default)
uint8_t *pPalette24; // RGB888 palette (optional)
uint8_t ucTransparent; // transparent color
uint8_t ucHasTransparency; // flag indicating the transparent color is in use
uint8_t ucDisposalMethod; // frame disposal method
uint8_t ucBackground; // background color
uint8_t ucIsGlobalPalette; // Flag to indicate that a global palette, rather than a local palette is being used
} GIFDRAW;
// Callback function prototypes
typedef int32_t (GIF_READ_CALLBACK)(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen);
typedef int32_t (GIF_SEEK_CALLBACK)(GIFFILE *pFile, int32_t iPosition);
typedef void (GIF_DRAW_CALLBACK)(GIFDRAW *pDraw);
typedef void * (GIF_OPEN_CALLBACK)(const char *szFilename, int32_t *pFileSize);
typedef void (GIF_CLOSE_CALLBACK)(void *pHandle);
typedef void * (GIF_ALLOC_CALLBACK)(uint32_t iSize);
typedef void (GIF_FREE_CALLBACK)(void *buffer);
//
// our private structure to hold a GIF image decode state
//
typedef struct gif_image_tag
{
int iWidth, iHeight, iCanvasWidth, iCanvasHeight;
int iX, iY; // GIF corner offset
int iBpp;
int iError; // last error
int iFrameDelay; // delay in milliseconds for this frame
int iRepeatCount; // NETSCAPE animation repeat count. 0=forever
int iXCount, iYCount; // decoding position in image (countdown values)
int iLZWOff; // current LZW data offset
int iLZWSize; // current quantity of data in the LZW buffer
int iCommentPos; // file offset of start of comment data
short sCommentLen; // length of comment
GIF_READ_CALLBACK *pfnRead;
GIF_SEEK_CALLBACK *pfnSeek;
GIF_DRAW_CALLBACK *pfnDraw;
GIF_OPEN_CALLBACK *pfnOpen;
GIF_CLOSE_CALLBACK *pfnClose;
GIFFILE GIFFile;
void *pUser;
unsigned char *pFrameBuffer;
unsigned char *pPixels, *pOldPixels;
unsigned char ucLineBuf[MAX_WIDTH]; // current line
unsigned char ucFileBuf[FILE_BUF_SIZE]; // holds temp data and pixel stack
unsigned short pPalette[(MAX_COLORS * 3)/2]; // can hold RGB565 or RGB888 - set in begin()
unsigned short pLocalPalette[(MAX_COLORS * 3)/2]; // color palettes for GIF images
unsigned char ucLZW[LZW_BUF_SIZE]; // holds 6 chunks (6x255) of GIF LZW data packed together
unsigned short usGIFTable[1<<MAX_CODE_SIZE];
unsigned char ucGIFPixels[(PIXEL_LAST*2)];
unsigned char bEndOfFrame;
unsigned char ucGIFBits, ucBackground, ucTransparent, ucCodeStart, ucMap, bUseLocalPalette;
unsigned char ucPaletteType; // RGB565 or RGB888
unsigned char ucDrawType; // RAW or COOKED
} GIFIMAGE;
#ifdef __cplusplus
//
// The GIF class wraps portable C code which does the actual work
//
class AnimatedGIF
{
public:
int open(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw);
int openFLASH(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw);
int open(const char *szFilename, GIF_OPEN_CALLBACK *pfnOpen, GIF_CLOSE_CALLBACK *pfnClose, GIF_READ_CALLBACK *pfnRead, GIF_SEEK_CALLBACK *pfnSeek, GIF_DRAW_CALLBACK *pfnDraw);
void close();
void reset();
void begin(unsigned char ucPaletteType = GIF_PALETTE_RGB565_LE);
void begin(int iEndian, unsigned char ucPaletteType) { begin(ucPaletteType); };
int playFrame(bool bSync, int *delayMilliseconds, void *pUser = NULL);
int getCanvasWidth();
int allocFrameBuf(GIF_ALLOC_CALLBACK *pfnAlloc);
int setDrawType(int iType);
int freeFrameBuf(GIF_FREE_CALLBACK *pfnFree);
uint8_t *getFrameBuf();
int getCanvasHeight();
int getLoopCount();
int getInfo(GIFINFO *pInfo);
int getLastError();
int getComment(char *destBuffer);
private:
GIFIMAGE _gif;
};
#else
// C interface
int GIF_openRAM(GIFIMAGE *pGIF, uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw);
int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnDraw);
void GIF_close(GIFIMAGE *pGIF);
void GIF_begin(GIFIMAGE *pGIF, unsigned char ucPaletteType);
void GIF_reset(GIFIMAGE *pGIF);
int GIF_playFrame(GIFIMAGE *pGIF, int *delayMilliseconds, void *pUser);
int GIF_getCanvasWidth(GIFIMAGE *pGIF);
int GIF_getCanvasHeight(GIFIMAGE *pGIF);
int GIF_getComment(GIFIMAGE *pGIF, char *destBuffer);
int GIF_getInfo(GIFIMAGE *pGIF, GIFINFO *pInfo);
int GIF_getLastError(GIFIMAGE *pGIF);
int GIF_getLoopCount(GIFIMAGE *pGIF);
#endif // __cplusplus
// Due to unaligned memory causing an exception, we have to do these macros the slow way
#define INTELSHORT(p) ((*p) + (*(p+1)<<8))
#define INTELLONG(p) ((*p) + (*(p+1)<<8) + (*(p+2)<<16) + (*(p+3)<<24))
#define MOTOSHORT(p) (((*(p))<<8) + (*(p+1)))
#define MOTOLONG(p) (((*p)<<24) + ((*(p+1))<<16) + ((*(p+2))<<8) + (*(p+3)))
// Must be a 32-bit target processor
#define REGISTER_WIDTH 32
#endif // __ANIMATEDGIF__

View File

@@ -2,6 +2,7 @@
* @file lcd_api.h
* @brief API for LCD functionality
* @author Tim S.
* @author Lorenz C.
*/
#ifndef INC_LCD_API_H_
@@ -14,6 +15,13 @@
#include "log.h"
#include "../../Drivers/BSP/STM32746G-Discovery/stm32746g_discovery_lcd.h"
#include "llfs.h"
#include "gifdec.h"
/**
* @brief The maximum amount of GIFs that can be displayed at the same time
* @note This can't be higher than 255 (uint8_t)
*/
#define LCD_MAX_GIFS 5
#define LCD_BLUE LCD_COLOR_BLUE
#define LCD_GREEN LCD_COLOR_GREEN
@@ -56,6 +64,17 @@
extern LTDC_HandleTypeDef hLtdcHandler;
typedef struct {
GIFIMAGE gif;
uint8_t* src;
uint32_t x_pos;
uint32_t y_pos;
uint32_t last_frame_time;
int loop_count;
int cur_loop;
int frame_delay;
} lcd_gif_t;
/**
* @brief Initialise LCD
* Initialise the LCD screen with BackLight on or not
@@ -65,6 +84,12 @@ extern LTDC_HandleTypeDef hLtdcHandler;
*/
void lcd_init(bool bl_on);
/**
* @brief LCD task
* Task to be called in the main loop to update the LCD screen
*/
void lcd_task(void);
/**
* @brief Display text
* Display text on the LCD screen in a certain color. When text width exceeds BSP_LCD_GetXSize(),
@@ -127,4 +152,50 @@ void lcd_draw_img_from_fs(const char* name, uint32_t x_pos, uint32_t y_pos);
void lcd_clear(uint32_t color);
/**
* @brief Draw GIF image on screen from memory
* Draw GIF image from memory to the LCD screen at position X, Y
* @warning If the GIF has a loop count specified, it will stop after the specified amount of loops and the lcd_git_t handle will be invalid for further use
* @note Before drawing over a GIF, make sure to call lcd_stop_gif(), otherwise the GIF will keep overwriting the screen
*
* @param src Pointer to the GIF image data
* @param size The size of the GIF image data
* @param x_pos The X position on the screen
* @param y_pos The Y position on the screen
* @return A handle to the GIF image, NULL if the GIF could not be opened
*/
lcd_gif_t* lcd_draw_gif(uint8_t* src, size_t size, uint32_t x_pos, uint32_t y_pos);
/**
* @brief Draw GIF image on screen from filesystem
* Draw GIF image from filesystem to the LCD screen at position X, Y
* @warning If the GIF has a loop count specified, it will stop after the specified amount of loops and the lcd_git_t handle will be invalidated
* @note Before drawing over a GIF, make sure to call lcd_stop_gif(), otherwise the GIF will keep overwriting the screen
*
* @param name The filename of the GIF image
* @param x_pos The X position on the screen
* @param y_pos The Y position on the screen
* @return A handle to the GIF image, NULL if the GIF could not be opened
*/
lcd_gif_t* lcd_draw_gif_from_fs(const char* name, uint32_t x_pos, uint32_t y_pos);
/**
* @brief Stop a GIF from playing
* Frees the GIF slot and stops the GIF from playing
* Call this function before trying to draw over a GIF
* @warning Make sure the GIF is still playing, otherwise it might stop another GIF
*
* @param gif The handle to the GIF image
*/
void lcd_stop_gif(lcd_gif_t* gif);
/**
* @brief Check if a GIF is still playing
* @note It is possible that the GIF has stopped playing, but another GIF has taken its slot and is still playing
*
* @param gif The handle to the GIF image
* @return True if the GIF is still playing, false if not
*/
bool lcd_gif_is_playing(lcd_gif_t* gif);
#endif /* INC_LCD_API_H_ */

1014
project/Core/Src/gifdec.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,20 @@
* @file lcd_api.c
* @brief LCD API implementation
* @author Tim S.
* @author Lorenz C.
*/
#include "lcd_api.h"
static const char* TAG = "lcd_api";
static DMA2D_HandleTypeDef hDma2dHandler2;
static lcd_gif_t gifs[LCD_MAX_GIFS]; // Array of GIF slots
static lcd_gif_t* get_free_gif_slot(void);
static void gif_draw_cb(GIFDRAW* pDraw);
static inline void free_gif_slot(lcd_gif_t* gif);
void lcd_init(bool bl_on) {
LOG_INFO(TAG, "Init LCD");
@@ -28,7 +36,51 @@ void lcd_init(bool bl_on) {
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_12, GPIO_PIN_RESET);
}
void lcd_display_text(uint8_t* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT *font) {
void lcd_task(void) {
// Play the next frame of each GIF if it's time
for (uint8_t i = 0; i < LCD_MAX_GIFS; i++) {
int ret;
// Check if the GIF is in use
if (gifs[i].src == NULL) {
continue;
}
// Check if it's time to play the next frame
if (HAL_GetTick() - gifs[i].last_frame_time < gifs[i].frame_delay) {
continue;
}
// Play the next frame
ret = GIF_playFrame(&gifs[i].gif, &gifs[i].frame_delay, &gifs[i].gif);
if (ret == 0) { // No more frames
// Infinite loop
if (gifs[i].loop_count <= 0) {
GIF_reset(&gifs[i].gif);
continue;
}
// Loop again
if (gifs[i].cur_loop < gifs[i].loop_count) {
GIF_reset(&gifs[i].gif);
gifs[i].cur_loop++;
continue;
}
// No more loops, free slot
LOG_DEBUG(TAG, "GIF finished, freeing slot");
free_gif_slot(&gifs[i]);
} else if (ret == -1) { // Error
LOG_WARN(TAG, "GIF_playFrame error, freeing slot");
free_gif_slot(&gifs[i]);
}
// Update the last frame time
gifs[i].last_frame_time = HAL_GetTick();
}
}
void lcd_display_text(uint8_t* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT* font) {
LOG_INFO(TAG, "Display text: %s @x=%d,y=%d", text, x_pos, y_pos);
uint16_t tot_length = x_pos + (strlen(text) * font->Width);
@@ -61,13 +113,11 @@ void lcd_display_text(uint8_t* text, uint16_t x_pos, uint16_t y_pos, uint32_t co
}
void lcd_draw_raw_img(const void* p_src, uint32_t x_pos, uint32_t y_pos, uint32_t x_size, uint32_t y_size, uint32_t color_mode) {
uint32_t address = hLtdcHandler.LayerCfg[1].FBStartAdress + (((BSP_LCD_GetXSize() * y_pos) + x_pos) * (4));
void *p_dst = (void *)address;
void* p_dst = (void*)address;
hDma2dHandler2.Init.Mode = DMA2D_M2M_PFC;
hDma2dHandler2.Init.ColorMode = DMA2D_ARGB8888;
hDma2dHandler2.Init.Mode = DMA2D_M2M_PFC;
hDma2dHandler2.Init.ColorMode = DMA2D_ARGB8888;
hDma2dHandler2.Init.OutputOffset = BSP_LCD_GetXSize() - x_size;
hDma2dHandler2.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
@@ -83,7 +133,7 @@ void lcd_draw_raw_img(const void* p_src, uint32_t x_pos, uint32_t y_pos, uint32_
}
LOG_INFO(TAG, "DMA2D config layer");
if (HAL_DMA2D_ConfigLayer(&hDma2dHandler2, 1) != HAL_OK) {
LOG_CRIT(TAG, "HAL_DMA2D_ConfigLayer error");
LOG_CRIT(TAG, "HAL_DMA2D_ConfigLayer error");
return;
}
LOG_INFO(TAG, "DMA2D start");
@@ -102,7 +152,7 @@ void lcd_draw_bmp_img(uint8_t* bmp_buff, uint32_t x_pos, uint32_t y_pos) {
void lcd_draw_img_from_fs(const char* name, uint32_t x_pos, uint32_t y_pos) {
llfs_file_t* file = llfs_file_open(name);
if (file != NULL) {
BSP_LCD_DrawBitmap(x_pos, y_pos, file->data);
BSP_LCD_DrawBitmap(x_pos, y_pos, (uint8_t*)file->data);
return;
}
LOG_WARN(TAG, "File \"%s\" not found", file->name);
@@ -111,3 +161,128 @@ void lcd_draw_img_from_fs(const char* name, uint32_t x_pos, uint32_t y_pos) {
void lcd_clear(uint32_t color) {
BSP_LCD_Clear(color);
}
lcd_gif_t* lcd_draw_gif(uint8_t* src, size_t size, uint32_t x_pos, uint32_t y_pos) {
lcd_gif_t* gif;
// Get a free GIF slot
if ((gif = get_free_gif_slot()) == NULL) {
LOG_WARN(TAG, "No free GIF slots");
return NULL;
}
// Open the GIF and reset slot values
GIF_begin(&(gif->gif), GIF_PALETTE_RGB888);
if (GIF_openRAM(&(gif->gif), src, (int)size, gif_draw_cb)) {
gif->src = src;
gif->x_pos = x_pos;
gif->y_pos = y_pos;
gif->last_frame_time = 0;
gif->loop_count = GIF_getLoopCount(&(gif->gif));
gif->cur_loop = 1;
gif->frame_delay = 0;
gif->gif.ucDrawType = GIF_DRAW_RAW;
gif->gif.ucPaletteType = GIF_PALETTE_RGB888;
return gif;
}
// Failed to open GIF
LOG_WARN(TAG, "GIF_openRAM failed");
free_gif_slot(gif);
return NULL;
}
lcd_gif_t* lcd_draw_gif_from_fs(const char* name, uint32_t x_pos, uint32_t y_pos) {
lcd_gif_t* gif;
llfs_file_t* file;
// Get the file from llfs
file = llfs_file_open(name);
if (file == NULL) {
LOG_WARN(TAG, "File \"%s\" not found", name);
return NULL;
}
// Draw the GIF using the file data
gif = lcd_draw_gif((uint8_t*)file->data, file->len, x_pos, y_pos);
return gif;
}
void lcd_stop_gif(lcd_gif_t* gif) {
free_gif_slot(gif);
}
bool lcd_gif_is_playing(lcd_gif_t* gif) {
return gif->src != NULL;
}
/**
* @brief Get a free GIF slot
* Get the next GIF slot that is not in use
*
* @return Pointer to the GIF slot, NULL if none are available
*/
static lcd_gif_t* get_free_gif_slot(void) {
for (int i = 0; i < 5; i++) {
if (gifs[i].src == NULL) {
return &gifs[i];
}
}
return NULL;
}
/**
* @brief Free a GIF slot
* Release a GIF slot, and free the memory
*
* @param gif Pointer to the GIF slot to free
*/
static inline void free_gif_slot(lcd_gif_t* gif) {
gif->src = NULL;
GIF_close(&gif->gif);
}
/**
* @brief Callback function used by the GIF decoder
* This function is called by the GIF decoder to draw a new line of pixels
* @todo See if it's possible to use DMA2D for this
*
* @param pDraw Pointer to the GIFDRAW struct
*/
static void gif_draw_cb(GIFDRAW* pDraw) {
lcd_gif_t* gif = (lcd_gif_t*)pDraw->pUser;
uint8_t* palette = pDraw->pPalette24; // The RGB888 color palette
uint8_t* p_src = pDraw->pPixels; // Source pixel pointer
int y = pDraw->iY + pDraw->y; // Current line being drawn
// Calculate the destination address of the first pixel in the line
uint32_t address = hLtdcHandler.LayerCfg[1].FBStartAdress + (((BSP_LCD_GetXSize() * gif->y_pos) + gif->x_pos) * 4)
+ (y * (BSP_LCD_GetXSize() * 4));
// Restore the background
if (pDraw->ucDisposalMethod == 2) {
for (int x = 0; x < pDraw->iWidth; x++) {
if (p_src[x] == pDraw->ucTransparent) {
p_src[x] = pDraw->ucBackground;
}
}
pDraw->ucHasTransparency = 0;
}
// Draw each pixel in the line
for (int x = 0; x < pDraw->iWidth; x++) {
uint8_t pixel = *p_src++;
// Skip transparent pixels
if (pDraw->ucHasTransparency && pixel == pDraw->ucTransparent) {
continue;
}
// Get the color from the palette and convert it to ARGB8888
uint8_t *p = palette + (pixel * 3);
uint32_t color = (0xFF << 24) | (p[0] << 16) | (p[1] << 8) | p[2];
// Draw the pixel
((uint32_t *)address)[x] = color;
}
}

View File

@@ -136,6 +136,7 @@ int main(void)
/* USER CODE BEGIN 3 */
MX_LWIP_Process();
lcd_task();
}
/* USER CODE END 3 */
}