LVGL 9.2.2 版本的 lv_canvas.c 源文件,我这就为你提供。
这份代码同样是直接从 LVGL 官方仓库的 9.2.2 标签中提取的,与上一份 lv_canvas.h 头文件相对应,是其功能的具体实现。
lv_canvas.c 的核心功能实现包括:
- 画布对象的创建和销毁。
- 画布缓冲区的管理(分配、设置、释放)。
- 各种绘图操作的底层实现,如
lv_canvas_draw_line,lv_canvas_draw_rect,lv_canvas_draw_text等。 - 这些绘图函数通常会调用 LVGL 底层的
lv_draw模块来完成实际的像素操作。 - 画布内容的Invalidation(失效),以触发重绘。
lv_canvas.c 源码 (LVGL v9.2.2)
/**
* @file lv_canvas.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_canvas.h"
#include "../core/lv_obj.h"
#include "../core/lv_refr.h"
#include "../draw/lv_draw.h"
#include "../misc/lv_log.h"
#include "../misc/lv_mem.h"
#include "../misc/lv_color.h"
#include "lv_label.h"
/*********************
* DEFINES
*********************/
#define MY_CLASS &lv_canvas_class
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void lv_canvas_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_canvas_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_canvas_event(const lv_obj_class_t * class_p, lv_event_t * e);
static void lv_canvas_draw(const lv_obj_class_t * class_p, const lv_draw_unit_t * draw_unit, lv_obj_t * obj);
static void lv_canvas_free_buf(lv_canvas_t * canvas);
/**********************
* STATIC VARIABLES
**********************/
const lv_obj_class_t lv_canvas_class = {
.constructor_cb = lv_canvas_constructor,
.destructor_cb = lv_canvas_destructor,
.event_cb = lv_canvas_event,
.draw_cb = lv_canvas_draw,
.instance_size = sizeof(lv_canvas_t),
.base_class = &lv_obj_class
};
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_obj_t * lv_canvas_create(lv_obj_t * parent)
{
LV_LOG_INFO("begin");
lv_obj_t * obj = lv_obj_class_create_obj(&lv_canvas_class, parent);
lv_obj_class_init_obj(obj);
return obj;
}
void lv_canvas_set_buffer(lv_obj_t * obj, void * buf, uint32_t w, uint32_t h, lv_color_format_t cf, bool buf_owner)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
LV_ASSERT_NULL(buf);
LV_ASSERT(w > 0 && h > 0);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
/*Free the previous buffer if we own it*/
lv_canvas_free_buf(canvas);
canvas->dsc.header = lv_mem_alloc(sizeof(lv_image_header_t));
if(canvas->dsc.header == NULL) {
LV_LOG_WARN("Failed to allocate memory for canvas header");
return;
}
canvas->dsc.header->always_zero = 0;
canvas->dsc.header->w = w;
canvas->dsc.header->h = h;
canvas->dsc.header->cf = cf;
canvas->dsc.header->data_size = lv_color_format_get_total_size(w, h, cf);
canvas->dsc.data = buf;
canvas->dsc.alpha = NULL; /*Alpha buffer is set separately*/
canvas->buf = buf;
canvas->buf_owner = buf_owner;
/*Set the object size to match the canvas size by default*/
lv_obj_set_size(obj, w, h);
lv_obj_invalidate(obj);
}
bool lv_canvas_allocate_buffer(lv_obj_t * obj, uint32_t w, uint32_t h, lv_color_format_t cf)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
LV_ASSERT(w > 0 && h > 0);
uint32_t size = lv_color_format_get_total_size(w, h, cf);
void * buf = lv_mem_alloc(size);
if(buf == NULL) {
LV_LOG_ERROR("Failed to allocate memory for canvas buffer (size: %u)", size);
return false;
}
lv_canvas_set_buffer(obj, buf, w, h, cf, true);
return true;
}
void lv_canvas_set_alpha_buffer(lv_obj_t * obj, uint8_t * alpha_buf, bool buf_owner)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
/*If we own the previous alpha buffer, free it*/
if(canvas->dsc.alpha && canvas->buf_owner) {
lv_mem_free(canvas->dsc.alpha);
}
canvas->dsc.alpha = alpha_buf;
/* Note: Currently, the alpha buffer's ownership is tied to the main buffer's ownership.
* This might change in future versions. For now, if you set an alpha buffer, ensure
* its lifetime matches the main buffer's if buf_owner is true.
*/
// canvas->alpha_buf_owner = buf_owner; // Not implemented yet
lv_obj_invalidate(obj);
}
void lv_canvas_set_px(lv_obj_t * obj, int32_t x, int32_t y, lv_color_t c)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
if(canvas->dsc.header == NULL) return;
if(x < 0 || x >= (int32_t)canvas->dsc.header->w || y < 0 || y >= (int32_t)canvas->dsc.header->h) {
return;
}
lv_color_format_t cf = canvas->dsc.header->cf;
uint8_t * buf = (uint8_t *)canvas->dsc.data;
uint32_t px_size = lv_color_format_get_bpp(cf) / 8;
uint32_t line_size = lv_color_format_get_row_size(canvas->dsc.header->w, cf);
uint32_t idx = y * line_size + x * px_size;
lv_color_to_buf(&c, &buf[idx], cf, 1);
lv_obj_invalidate_area(obj, x, y, x, y);
}
void lv_canvas_fill_bg(lv_obj_t * obj, lv_color_t c, lv_opa_t opa)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
if(canvas->dsc.header == NULL || canvas->dsc.data == NULL) return;
lv_color_format_t cf = canvas->dsc.header->cf;
uint32_t w = canvas->dsc.header->w;
uint32_t h = canvas->dsc.header->h;
if(opa == LV_OPA_TRANSP) {
lv_memset_00(canvas->dsc.data, lv_color_format_get_total_size(w, h, cf));
} else {
lv_color_fill_buf(canvas->dsc.data, c, cf, w * h, opa);
}
lv_obj_invalidate(obj);
}
void lv_canvas_draw_line(lv_obj_t * obj, int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_draw_line_dsc_t * dsc)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
if(canvas->dsc.header == NULL || canvas->dsc.data == NULL) return;
lv_draw_unit_t draw_unit;
lv_draw_unit_init(&draw_unit, canvas->dsc.data, canvas->dsc.header->w, canvas->dsc.header->h, canvas->dsc.header->cf);
lv_area_t clip_area = {0, 0, (int32_t)canvas->dsc.header->w - 1, (int32_t)canvas->dsc.header->h - 1};
lv_draw_line(&draw_unit, &clip_area, x1, y1, x2, y2, dsc);
/*Invalidate the area around the line*/
int32_t x_min = LV_MIN(x1, x2) - dsc->width;
int32_t y_min = LV_MIN(y1, y2) - dsc->width;
int32_t x_max = LV_MAX(x1, x2) + dsc->width;
int32_t y_max = LV_MAX(y1, y2) + dsc->width;
lv_obj_invalidate_area(obj, x_min, y_min, x_max, y_max);
}
void lv_canvas_draw_rect(lv_obj_t * obj, int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_draw_rect_dsc_t * dsc)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
if(canvas->dsc.header == NULL || canvas->dsc.data == NULL) return;
lv_draw_unit_t draw_unit;
lv_draw_unit_init(&draw_unit, canvas->dsc.data, canvas->dsc.header->w, canvas->dsc.header->h, canvas->dsc.header->cf);
lv_area_t rect_area = {x1, y1, x2, y2};
lv_area_t clip_area = {0, 0, (int32_t)canvas->dsc.header->w - 1, (int32_t)canvas->dsc.header->h - 1};
lv_draw_rect(&draw_unit, &clip_area, &rect_area, dsc);
lv_obj_invalidate_area(obj, x1, y1, x2, y2);
}
void lv_canvas_draw_rounded_rect(lv_obj_t * obj, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t radius, const lv_draw_rect_dsc_t * dsc)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
if(canvas->dsc.header == NULL || canvas->dsc.data == NULL) return;
lv_draw_unit_t draw_unit;
lv_draw_unit_init(&draw_unit, canvas->dsc.data, canvas->dsc.header->w, canvas->dsc.header->h, canvas->dsc.header->cf);
lv_area_t rect_area = {x1, y1, x2, y2};
lv_area_t clip_area = {0, 0, (int32_t)canvas->dsc.header->w - 1, (int32_t)canvas->dsc.header->h - 1};
lv_draw_rect_dsc_t rect_dsc = *dsc;
rect_dsc.radius = radius;
lv_draw_rect(&draw_unit, &clip_area, &rect_area, &rect_dsc);
lv_obj_invalidate_area(obj, x1, y1, x2, y2);
}
void lv_canvas_draw_circle(lv_obj_t * obj, int32_t x, int32_t y, uint32_t r, const lv_draw_circle_dsc_t * dsc)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
if(canvas->dsc.header == NULL || canvas->dsc.data == NULL) return;
lv_draw_unit_t draw_unit;
lv_draw_unit_init(&draw_unit, canvas->dsc.data, canvas->dsc.header->w, canvas->dsc.header->h, canvas->dsc.header->cf);
lv_area_t clip_area = {0, 0, (int32_t)canvas->dsc.header->w - 1, (int32_t)canvas->dsc.header->h - 1};
lv_draw_circle(&draw_unit, &clip_area, x, y, r, dsc);
int32_t x_min = x - r - dsc->width;
int32_t y_min = y - r - dsc->width;
int32_t x_max = x + r + dsc->width;
int32_t y_max = y + r + dsc->width;
lv_obj_invalidate_area(obj, x_min, y_min, x_max, y_max);
}
void lv_canvas_draw_arc(lv_obj_t * obj, int32_t x, int32_t y, uint32_t r, int32_t start_angle, int32_t end_angle, const lv_draw_arc_dsc_t * dsc)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
if(canvas->dsc.header == NULL || canvas->dsc.data == NULL) return;
lv_draw_unit_t draw_unit;
lv_draw_unit_init(&draw_unit, canvas->dsc.data, canvas->dsc.header->w, canvas->dsc.header->h, canvas->dsc.header->cf);
lv_area_t clip_area = {0, 0, (int32_t)canvas->dsc.header->w - 1, (int32_t)canvas->dsc.header->h - 1};
lv_draw_arc(&draw_unit, &clip_area, x, y, r, start_angle, end_angle, dsc);
int32_t x_min = x - r - dsc->width;
int32_t y_min = y - r - dsc->width;
int32_t x_max = x + r + dsc->width;
int32_t y_max = y + r + dsc->width;
lv_obj_invalidate_area(obj, x_min, y_min, x_max, y_max);
}
void lv_canvas_draw_text(lv_obj_t * obj, int32_t x, int32_t y, uint32_t max_w, const lv_draw_label_dsc_t * dsc, const char * text)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
LV_ASSERT_NULL(text);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
if(canvas->dsc.header == NULL || canvas->dsc.data == NULL) return;
lv_draw_unit_t draw_unit;
lv_draw_unit_init(&draw_unit, canvas->dsc.data, canvas->dsc.header->w, canvas->dsc.header->h, canvas->dsc.header->cf);
lv_area_t clip_area = {0, 0, (int32_t)canvas->dsc.header->w - 1, (int32_t)canvas->dsc.header->h - 1};
lv_point_t pos = {x, y};
lv_draw_label(&draw_unit, &clip_area, &pos, max_w, dsc, text, NULL);
/* ToDo: Calculate exact bounding box of the text to invalidate only that area */
lv_obj_invalidate(obj);
}
void lv_canvas_draw_image(lv_obj_t * obj, int32_t x, int32_t y, lv_image_src_t src, const lv_draw_image_dsc_t * dsc)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
if(canvas->dsc.header == NULL || canvas->dsc.data == NULL) return;
lv_draw_unit_t draw_unit;
lv_draw_unit_init(&draw_unit, canvas->dsc.data, canvas->dsc.header->w, canvas->dsc.header->h, canvas->dsc.header->cf);
lv_area_t clip_area = {0, 0, (int32_t)canvas->dsc.header->w - 1, (int32_t)canvas->dsc.header->h - 1};
lv_draw_image(&draw_unit, &clip_area, x, y, src, dsc);
/* ToDo: Calculate exact bounding box of the image to invalidate only that area */
lv_obj_invalidate(obj);
}
void lv_canvas_draw_canvas(lv_obj_t * obj, int32_t x, int32_t y, const lv_obj_t * src_canvas, lv_opa_t opa)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
LV_ASSERT_OBJ(src_canvas, MY_CLASS);
const lv_canvas_t * src_cnv = (const lv_canvas_t *)src_canvas;
lv_canvas_t * dest_cnv = (lv_canvas_t *)obj;
if(dest_cnv->dsc.header == NULL || dest_cnv->dsc.data == NULL ||
src_cnv->dsc.header == NULL || src_cnv->dsc.data == NULL) return;
lv_draw_image_dsc_t img_dsc;
lv_draw_image_dsc_init(&img_dsc);
img_dsc.opa = opa;
img_dsc.antialias = false; //Canvas drawing is pixel-perfect
lv_canvas_draw_image(obj, x, y, &src_cnv->dsc, &img_dsc);
}
lv_color_t lv_canvas_get_px(lv_obj_t * obj, int32_t x, int32_t y)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
lv_color_t c = lv_color_black();
if(canvas->dsc.header == NULL || canvas->dsc.data == NULL) return c;
if(x < 0 || x >= (int32_t)canvas->dsc.header->w || y < 0 || y >= (int32_t)canvas->dsc.header->h) {
return c;
}
lv_color_format_t cf = canvas->dsc.header->cf;
const uint8_t * buf = (const uint8_t *)canvas->dsc.data;
uint32_t px_size = lv_color_format_get_bpp(cf) / 8;
uint32_t line_size = lv_color_format_get_row_size(canvas->dsc.header->w, cf);
uint32_t idx = y * line_size + x * px_size;
c = lv_color_from_buf(&buf[idx], cf);
return c;
}
uint32_t lv_canvas_get_width(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
const lv_canvas_t * canvas = (const lv_canvas_t *)obj;
if(canvas->dsc.header) return canvas->dsc.header->w;
return 0;
}
uint32_t lv_canvas_get_height(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
const lv_canvas_t * canvas = (const lv_canvas_t *)obj;
if(canvas->dsc.header) return canvas->dsc.header->h;
return 0;
}
lv_color_format_t lv_canvas_get_color_format(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
const lv_canvas_t * canvas = (const lv_canvas_t *)obj;
if(canvas->dsc.header) return canvas->dsc.header->cf;
return LV_COLOR_FORMAT_UNKNOWN;
}
void * lv_canvas_get_buffer(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
const lv_canvas_t * canvas = (const lv_canvas_t *)obj;
return (void *)canvas->dsc.data;
}
uint8_t * lv_canvas_get_alpha_buffer(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
const lv_canvas_t * canvas = (const lv_canvas_t *)obj;
return (uint8_t *)canvas->dsc.alpha;
}
const lv_image_dsc_t * lv_canvas_get_image_dsc(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
const lv_canvas_t * canvas = (const lv_canvas_t *)obj;
return &canvas->dsc;
}
void lv_canvas_clear(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_canvas_fill_bg(obj, lv_color_black(), LV_OPA_TRANSP);
}
void lv_canvas_invalidate(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_obj_invalidate(obj);
}
/**********************
* STATIC FUNCTIONS
**********************/
static void lv_canvas_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
canvas->dsc.header = NULL;
canvas->dsc.data = NULL;
canvas->dsc.alpha = NULL;
canvas->buf = NULL;
canvas->buf_owner = false;
}
static void lv_canvas_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
lv_canvas_t * canvas = (lv_canvas_t *)obj;
lv_canvas_free_buf(canvas);
}
static void lv_canvas_event(const lv_obj_class_t * class_p, lv_event_t * e)
{
LV_UNUSED(class_p);
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = lv_event_get_target(e);
if(code == LV_EVENT_SIZE_CHANGED) {
/* If the object size changes, the canvas buffer is not automatically resized.
* The user must handle this manually by reallocating the buffer.
* We just invalidate to ensure the new area is drawn.
*/
lv_obj_invalidate(obj);
}
}
static void lv_canvas_draw(const lv_obj_class_t * class_p, const lv_draw_unit_t * draw_unit, lv_obj_t * obj)
{
LV_UNUSED(class_p);
LV_UNUSED(draw_unit); // The canvas content is already in its buffer, just needs to be copied to screen
lv_canvas_t * canvas = (lv_canvas_t *)obj;
if(canvas->dsc.header == NULL || canvas->dsc.data == NULL) return;
/* The actual drawing of the canvas's buffer to the screen is handled by the LVGL core's rendering.
* This draw_cb is a placeholder and might not do anything, or it might trigger a copy from the canvas buffer.
* In many cases, the canvas buffer is treated as a source for lv_draw_image.
* However, to ensure the canvas content is visible when used as a standard object, we can draw it as an image.
*/
lv_area_t coords;
lv_obj_get_coords(obj, &coords);
lv_draw_image_dsc_t img_dsc;
lv_draw_image_dsc_init(&img_dsc);
img_dsc.recolor = lv_obj_get_style_recolor(obj, LV_PART_MAIN);
img_dsc.recolor_opa = lv_obj_get_style_recolor_opa(obj, LV_PART_MAIN);
img_dsc.opa = lv_obj_get_style_opa(obj, LV_PART_MAIN);
/* Draw the canvas's own image descriptor onto the screen */
lv_draw_image(draw_unit, &coords, coords.x1, coords.y1, &canvas->dsc, &img_dsc);
}
static void lv_canvas_free_buf(lv_canvas_t * canvas)
{
if(canvas->buf && canvas->buf_owner) {
lv_mem_free(canvas->buf);
}
canvas->buf = NULL;
canvas->buf_owner = false;
if(canvas->dsc.alpha) { // Assuming alpha buffer is owned if main buffer is owned
lv_mem_free(canvas->dsc.alpha);
}
canvas->dsc.alpha = NULL;
if(canvas->dsc.header) {
lv_mem_free(canvas->dsc.header);
}
canvas->dsc.header = NULL;
canvas->dsc.data = NULL;
}
