From ea5e75d8fc554736997fcf63657666e2952983a8 Mon Sep 17 00:00:00 2001 From: bartool Date: Sun, 26 Mar 2023 09:00:40 +0200 Subject: [PATCH] feature: display_gfx library for displaying graphics on the screen --- firmware/shared_libs/display/display_gfx.c | 423 +++++++++++++++++++++ firmware/shared_libs/display/display_gfx.h | 63 +++ 2 files changed, 486 insertions(+) create mode 100644 firmware/shared_libs/display/display_gfx.c create mode 100644 firmware/shared_libs/display/display_gfx.h diff --git a/firmware/shared_libs/display/display_gfx.c b/firmware/shared_libs/display/display_gfx.c new file mode 100644 index 0000000..ab482cf --- /dev/null +++ b/firmware/shared_libs/display/display_gfx.c @@ -0,0 +1,423 @@ +#include "main.h" +#include "display_gfx.h" + +typedef struct +{ + uint16_t bitmap_max_idx; + uint8_t buf_row_first; + uint8_t buf_row_last; + uint8_t buf_col_first; + uint8_t buf_col_last; + uint8_t buf_mask_top; + uint8_t buf_mask_bottom; + uint8_t bitmap_col; + uint8_t bitmap_row_first; + uint8_t bitmap_row_last; + uint8_t bitmap_shift; +} buf_bitmap_boundry_t; + +/** + * @brief Draw one pixel in a buffer. + * + * @param disp A pointer to display struct + * @param x Top-most x coordinate + * @param y Top-most y coordinate + * @param color Color of pixel WHITE(0), BLACK(1) or INVERSE(2) + */ +void DISP_drawPixel(GFX_display_t *disp, uint8_t x, uint8_t y, GFX_Color_t color) +{ + if (x > disp->width || y > disp->height) + return; + + switch (color) + { + case GFX_WHITE: + disp->buffor[(y / 8) * disp->width + x] |= (1 << (y % 8)); + break; + case GFX_BLACK: + disp->buffor[(y / 8) * disp->width + x] &= ~(1 << (y % 8)); + break; + case GFX_INVERSE: + disp->buffor[(y / 8) * disp->width + x] ^= (1 << (y % 8)); + break; + default: + break; + } +} + +/** + * @brief Draw a vertical line. + * + * @param disp A pointer to display struct + * @param x0 Start point x coordinate + * @param y0 Start point y coordinate + * @param x1 End point x coordinate + * @param y1 End point y coordinate + * @param color Color of pixel WHITE(0), BLACK(1) or INVERSE(2) + */ +void DISP_drawSlashLine(GFX_display_t *disp, int16_t x0, int16_t y0, int16_t x1, int16_t y1, GFX_Color_t color) +{ + uint8_t steep = _diff(y1, y0) > _diff(x1, x0); // bool + if (steep) + { + _swap_int16_t(x0, y0); + _swap_int16_t(x1, y1); + } + + if (x0 > x1) + { + _swap_int16_t(x0, x1); + _swap_int16_t(y0, y1); + } + + int16_t dx = x1 - x0; + int16_t dy = _diff(y1, y0); + int16_t err = dx >> 1; + int16_t step = (y0 < y1) ? 1 : -1; + + for (; x0 <= x1; x0++) + { + if (steep) + { + DISP_drawPixel(disp, y0, x0, color); + } + else + { + DISP_drawPixel(disp, x0, y0, color); + } + err -= dy; + if (err < 0) + { + err += dx; + y0 += step; + } + } +} + +/** + * @brief Write a perfectly vertical line + * @param disp A pointer to display struct + * @param x Top-most x coordinate + * @param y Top-most y coordinate + * @param height Height in pixels + * @param color Color of pixel WHITE(0), BLACK(1) or INVERSE(2) + */ +void DISP_drawVerticalLine(GFX_display_t *disp, int16_t x, int16_t y, int16_t height, GFX_Color_t color) +{ + for (int16_t i = y; i < y + height; i++) + { + DISP_drawPixel(disp, x, i, color); + } +} + +/** + @brief Write a perfectly horizontal line + @param disp A pointer to display struct + @param x Left-most x coordinate + @param y Left-most y coordinate + @param width Width in pixels + @param color Color of pixel WHITE(0), BLACK(1) or INVERSE(2) +*/ +void DISP_drawHorizontalLine(GFX_display_t *disp, int16_t x, int16_t y, int16_t width, GFX_Color_t color) +{ + for (int16_t i = x; i < x + width; i++) + { + DISP_drawPixel(disp, i, y, color); + } +} + +/** + * @brief Draw a rectangle with no fill color + * @param disp A pointer to display struct + * @param x Top left corner x coordinate + * @param y Top left corner y coordinate + * @param width Width in pixels + * @param height Height in pixels + * @param radius Radius of corner rounding + * @param color Color of pixel WHITE(0), BLACK(1) or INVERSE(2) + */ +void DISP_drawRect(GFX_display_t *disp, int16_t x, int16_t y, int16_t width, int16_t height, GFX_Color_t color) +{ + DISP_drawHorizontalLine(disp, x, y, width, color); + DISP_drawHorizontalLine(disp, x, y + height - 1, width, color); + DISP_drawVerticalLine(disp, x, y, height, color); + DISP_drawVerticalLine(disp, x + width - 1, y, height, color); +} + +/*! + @brief Draw a circle outline + @param disp A pointer to display struct + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param radius Radius of circle + @param color Color of pixel WHITE(0), BLACK(1) or INVERSE(2) +*/ +void DISP_drawCircle(GFX_display_t *disp, int16_t x0, int16_t y0, uint8_t radius, GFX_Color_t color) +{ + + int16_t f = 1 - radius; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * radius; + int16_t x = 0; + int16_t y = radius; + + DISP_drawPixel(disp, x0, y0 + radius, color); + DISP_drawPixel(disp, x0, y0 - radius, color); + DISP_drawPixel(disp, x0 + radius, y0, color); + DISP_drawPixel(disp, x0 - radius, y0, color); + + while (x < y) + { + if (f >= 0) + { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + + DISP_drawPixel(disp, x0 + x, y0 + y, color); + DISP_drawPixel(disp, x0 - x, y0 + y, color); + DISP_drawPixel(disp, x0 + x, y0 - y, color); + DISP_drawPixel(disp, x0 - x, y0 - y, color); + DISP_drawPixel(disp, x0 + y, y0 + x, color); + DISP_drawPixel(disp, x0 - y, y0 + x, color); + DISP_drawPixel(disp, x0 + y, y0 - x, color); + DISP_drawPixel(disp, x0 - y, y0 - x, color); + } +} + +/*! + @brief Quarter-circle drawer, used to do circles and roundrects + @param disp A pointer to display struct + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param radius Radius of circle + @param corner Mask bit to indicate which quarters of the circle we're doing + @param color Color of pixel WHITE(0), BLACK(1) or INVERSE(2) +*/ +void DISP_drawQuarterCircle(GFX_display_t *disp, int16_t x0, int16_t y0, uint8_t radius, GFX_CircCorners_t corner, GFX_Color_t color) +{ + int16_t f = 1 - radius; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * radius; + int16_t x = 0; + int16_t y = radius; + + while (x < y) + { + if (f >= 0) + { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + if (corner & BOTTOM_LEFT) + { + DISP_drawPixel(disp, x0 + x, y0 + y, color); + DISP_drawPixel(disp, x0 + y, y0 + x, color); + } + if (corner & BOTTOM_RIGHT) + { + DISP_drawPixel(disp, x0 + x, y0 - y, color); + DISP_drawPixel(disp, x0 + y, y0 - x, color); + } + if (corner & TOP_LEFT) + { + DISP_drawPixel(disp, x0 - y, y0 + x, color); + DISP_drawPixel(disp, x0 - x, y0 + y, color); + } + if (corner & TOP_RIGHT) + { + DISP_drawPixel(disp, x0 - y, y0 - x, color); + DISP_drawPixel(disp, x0 - x, y0 - y, color); + } + } +} + +/** + * @brief Draw a rounded rectangle with no fill color + * @param disp A pointer to display struct + * @param x Top left corner x coordinate + * @param y Top left corner y coordinate + * @param width Width in pixels + * @param height Height in pixels + * @param radius Radius of corner rounding + * @param color Color of pixel WHITE(0), BLACK(1) or INVERSE(2) + */ +void DISP_drawRoundRect(GFX_display_t *disp, int16_t x, int16_t y, int16_t width, int16_t height, int16_t radius, GFX_Color_t color) +{ + int16_t max_radius = ((width < height) ? width : height) / 2; // 1/2 minor axis + if (radius > max_radius) + radius = max_radius; + // smarter version + DISP_drawHorizontalLine(disp, x + radius, y, width - 2 * radius, color); // Top + DISP_drawHorizontalLine(disp, x + radius, y + height - 1, width - 2 * radius, color); // Bottom + DISP_drawVerticalLine(disp, x, y + radius, height - 2 * radius, color); // Left + DISP_drawVerticalLine(disp, x + width - 1, y + radius, height - 2 * radius, color); // Right + // draw four corners + DISP_drawQuarterCircle(disp, x + radius, y + radius, radius, 1, color); + DISP_drawQuarterCircle(disp, x + width - radius - 1, y + radius, radius, 2, color); + DISP_drawQuarterCircle(disp, x + width - radius - 1, y + height - radius - 1, radius, 4, color); + DISP_drawQuarterCircle(disp, x + radius, y + height - radius - 1, radius, 8, color); +} + +static void _getBoundry(GFX_display_t *disp, buf_bitmap_boundry_t *boundry, uint8_t bitmap_width, uint8_t bitmap_height, int8_t pos_x, int8_t pos_y) +{ + if (pos_x < 0) + { + boundry->bitmap_col = pos_x * -1; + boundry->buf_col_first = 0; + } + else + { + boundry->bitmap_col = 0; + boundry->buf_col_first = pos_x; + } + + if (pos_y < 0) + { + boundry->bitmap_shift = 8 + (pos_y % 8); + boundry->bitmap_row_first = (pos_y / 8) * (-1) + 1; + boundry->buf_row_first = 0; + boundry->buf_mask_top = 0; + } + else + { + boundry->bitmap_shift = pos_y % 8; + boundry->bitmap_row_first = 0; + boundry->buf_row_first = pos_y / 8; + boundry->buf_mask_top = 0xFF >> (8 - boundry->bitmap_shift); + } + boundry->buf_mask_bottom = 0xFF << ((pos_y + bitmap_height) % 8); + if (boundry->buf_mask_bottom == 0xFF) + { + boundry->buf_mask_bottom = 0; + } + + if ((bitmap_width + pos_x) > disp->width) + { + boundry->buf_col_last = disp->width; + } + else + { + boundry->buf_col_last = bitmap_width + pos_x; + } + + if (bitmap_height + pos_y > disp->height) + { + boundry->buf_row_last = disp->height / 8; + } + else + { + boundry->buf_row_last = (bitmap_height + pos_y + 7) / 8; + } + + boundry->bitmap_row_last = (pos_y + bitmap_height) / 8; + boundry->bitmap_max_idx = bitmap_width * ((bitmap_height + 7) / 8); +} + +static inline uint8_t _getBitmapByte(const uint8_t *bitmap, uint16_t index, GFX_BitmapColor_t color) +{ + switch (color) + { + case BM_INVERSE: + return ~(bitmap[index]); + case BM_FILL_WHITE: + return 0xFF; + case BM_FILL_BLACK: + return 0x00; + default: + return bitmap[index]; + } +} + +/** + * @brief Draw a 1-bit image at the specified (x,y) position. + * @param disp A pointer to display struct + * @param bitmap byte array with monochrome bitmap + * @param bitmap_width Width in pixels + * @param bitmap_height Height in pixels + * @param pos_x Top left corner x coordinate + * @param pos_y Top left corner y coordinate + * @param color Color of pixel BM_NORMAL or BM_INVERSE for bitmap. BM_WHITE or BM_BLACK for fill region. + */ +void DISP_drawBitmap(GFX_display_t *disp, GFX_bitmap_t *bitmap, int8_t pos_x, int8_t pos_y, GFX_BitmapColor_t color) +{ + if (bitmap->width + pos_x < 0 || bitmap->height + pos_y < 0) + return; + + uint16_t tmp_buf16, bitmap_idx, buf_idx; + uint8_t tmp_bitmap, bitmap_row; + + buf_bitmap_boundry_t b; + _getBoundry(disp, &b, bitmap->width, bitmap->height, pos_x, pos_y); + + for (uint8_t col = b.buf_col_first; col < b.buf_col_last; col++, b.bitmap_col++) + { + tmp_buf16 = 0; + bitmap_row = b.bitmap_row_first; + + if (b.bitmap_row_first > 0) + { + tmp_buf16 = _getBitmapByte(bitmap->bitmap, bitmap->width * (b.bitmap_row_first - 1) + b.bitmap_col, color) >> (8 - b.bitmap_shift); + } + else + { + tmp_buf16 = disp->buffor[b.buf_row_first * disp->width + col] & b.buf_mask_top; + } + + for (uint8_t buf_row = b.buf_row_first; buf_row < b.buf_row_last; buf_row++, bitmap_row++) + { + bitmap_idx = bitmap->width * bitmap_row + b.bitmap_col; + buf_idx = buf_row * disp->width + col; + + if (bitmap_idx < b.bitmap_max_idx) + { + tmp_bitmap = _getBitmapByte(bitmap->bitmap, bitmap_idx, color); + tmp_buf16 |= tmp_bitmap << b.bitmap_shift; + } + + if (b.bitmap_row_last == buf_row) + { + disp->buffor[buf_idx] = (disp->buffor[buf_idx] & b.buf_mask_bottom) | (tmp_buf16 & ~(b.buf_mask_bottom)); + } + else + { + disp->buffor[buf_idx] = (uint8_t)tmp_buf16; + } + tmp_buf16 = tmp_buf16 >> 8; + } + } +} + +void DISP_drawFillRect(GFX_display_t *disp, int16_t x, int16_t y, int16_t width, int16_t height) +{ + GFX_bitmap_t area = { + .height = height, + .width = width, + .bitmap = NULL, + }; + DISP_drawBitmap(disp, &area, x, y, BM_FILL_WHITE); +} + +void DISP_clearRegion(GFX_display_t *disp, int16_t x, int16_t y, int16_t width, int16_t height) +{ + GFX_bitmap_t area = { + .height = height, + .width = width, + .bitmap = NULL, + }; + DISP_drawBitmap(disp, &area, x, y, BM_FILL_BLACK); +} + +void DISP_clearScreen(GFX_display_t *disp) +{ + GFX_bitmap_t area = {.height = disp->height, .width = disp->width, .bitmap = NULL}; + DISP_drawBitmap(disp, &area, 0, 0, BM_FILL_BLACK); +} \ No newline at end of file diff --git a/firmware/shared_libs/display/display_gfx.h b/firmware/shared_libs/display/display_gfx.h new file mode 100644 index 0000000..9e35d91 --- /dev/null +++ b/firmware/shared_libs/display/display_gfx.h @@ -0,0 +1,63 @@ +#pragma once + +#ifndef _swap_int16_t +#define _swap_int16_t(a, b) \ + { \ + int16_t t = a; \ + a = b; \ + b = t; \ + } +#endif +#ifndef _diff +#define _diff(a, b) ((a > b) ? (a - b) : (b - a)) +#endif + +typedef enum +{ + GFX_WHITE, + GFX_BLACK, + GFX_INVERSE +} GFX_Color_t; + +typedef enum +{ + BM_FILL_WHITE, + BM_FILL_BLACK, + BM_NORMAL, + BM_INVERSE +} GFX_BitmapColor_t; + +typedef enum +{ + TOP_RIGHT = 1, + BOTTOM_RIGHT = 2, + BOTTOM_LEFT = 4, + TOP_LEFT = 8 +} GFX_CircCorners_t; + +typedef struct +{ + uint8_t width; + uint8_t height; + uint8_t *buffor; +} GFX_display_t; + +typedef struct +{ + uint8_t width; + uint8_t height; + const uint8_t *bitmap; +} GFX_bitmap_t; + +void DISP_drawPixel(GFX_display_t *disp, uint8_t x, uint8_t y, GFX_Color_t color); +void DISP_drawVerticalLine(GFX_display_t *disp, int16_t x, int16_t y, int16_t height, GFX_Color_t color); +void DISP_drawHorizontalLine(GFX_display_t *disp, int16_t x, int16_t y, int16_t width, GFX_Color_t color); +void DISP_drawSlashLine(GFX_display_t *disp, int16_t x0, int16_t y0, int16_t x1, int16_t y1, GFX_Color_t color); +void DISP_drawRect(GFX_display_t *disp, int16_t x, int16_t y, int16_t width, int16_t height, GFX_Color_t color); +void DISP_drawFillRect(GFX_display_t *disp, int16_t x, int16_t y, int16_t width, int16_t height); +void DISP_drawCircle(GFX_display_t *disp, int16_t x0, int16_t y0, uint8_t radius, GFX_Color_t color); +void DISP_drawQuarterCircle(GFX_display_t *disp, int16_t x0, int16_t y0, uint8_t radius, GFX_CircCorners_t corner, GFX_Color_t color); +void DISP_drawRoundRect(GFX_display_t *disp, int16_t x, int16_t y, int16_t width, int16_t height, int16_t radius, GFX_Color_t color); +void DISP_drawBitmap(GFX_display_t *disp, GFX_bitmap_t *bitmap, int8_t pos_x, int8_t pos_y, GFX_BitmapColor_t color); +void DISP_clearRegion(GFX_display_t *disp, int16_t x, int16_t y, int16_t width, int16_t height); +void DISP_clearScreen(GFX_display_t *disp);