什么是贝塞尔曲线
贝塞尔曲线(Bézier Curve,也被称为贝塞尔多项式(Bézier Polynomial),是由一系列控制点(Control Point)所定义的一条平滑曲线。Pierre Bézier于1960年开始利用该曲线设计雷诺的车身线条,故命名为贝塞尔曲线。目前,贝塞尔曲线被广泛应用于图形设计、路径优化(无人机、无人驾驶相关)等诸多相关领域中。
贝塞尔具体描述,可以搜索,网上也是一大把
贝塞尔曲线之购物车动画效果_11397799的技术博客_51CTO博客
lvgl 贝塞尔函数(三阶函数):
lvgl提供了三阶贝塞尔函数(即四个点绘制图形)
/**
* Calculate a value of a Cubic Bezier function.
* @param t time in range of [0..LV_BEZIER_VAL_MAX]
* @param u0 start values in range of [0..LV_BEZIER_VAL_MAX]
* @param u1 control value 1 values in range of [0..LV_BEZIER_VAL_MAX]
* @param u2 control value 2 in range of [0..LV_BEZIER_VAL_MAX]
* @param u3 end values in range of [0..LV_BEZIER_VAL_MAX]
* @return the value calculated from the given parameters in range of [0..LV_BEZIER_VAL_MAX]
*/
#define LV_BEZIER_VAL_MAX 1024 /**< Max time in Bezier functions (not [0..1] to use integers)*/
#define LV_BEZIER_VAL_SHIFT 10 /**< log2(LV_BEZIER_VAL_MAX): used to normalize up scaled values*/
uint32_t lv_bezier3(uint32_t t, uint32_t u0, uint32_t u1, uint32_t u2, uint32_t u3)
{
uint32_t t_rem = LV_BEZIER_VAL_MAX - t;
uint32_t t_rem2 = (t_rem * t_rem) >> 10;
uint32_t t_rem3 = (t_rem2 * t_rem) >> 10;
uint32_t t2 = (t * t) >> 10;
uint32_t t3 = (t2 * t) >> 10;
uint32_t v1 = (t_rem3 * u0) >> 10;
uint32_t v2 = (3 * t_rem2 * t * u1) >> 20;
uint32_t v3 = (3 * t_rem * t2 * u2) >> 20;
uint32_t v4 = (t3 * u3) >> 10;
return v1 + v2 + v3 + v4;
}
注:LV_BEZIER_VAL_MAX 参数值为什么定义是:2^n,这里是为了在嵌入式中可以通过移位,优化算法(毕竟做除法运算量是相当大的),另外很多时候分两次移位,因为uint32_t 做乘法的时候精度丢失。
t:(0~LV_BEZIER_VAL_MAX) 可以理解为横坐标
u0,u1,u2,u3 四个点纵坐标
v1 + v2 + v3 + v4: 算出当前绘制点的纵坐标
四阶绘制效果
贝塞尔函数(二阶函数):
自己手搓优化的二阶贝塞尔函数
转成C语言优化过后如下:
#define LV_BEZIER_VAL_MAX 1024
uint32_t lv_bezier2(uint32_t t, uint32_t u0, uint32_t u1, uint32_t u2)
{
uint32_t t_rem = MAX_TIME - t;
uint32_t t_rem2 = (t_rem * t_rem) >> 10;
uint32_t t2 = (t * t) >> 10;
uint32_t v1 = (t_rem2 * u0) >> 10;
uint32_t v2 = (2 * u1 * t * t_rem) >> 20;
uint32_t v3 = (t2 * u2) >> 10;
return v1 + v2 + v3;
}
三阶绘制效果
绘制代码如下
/**************************************************START OF FILE*****************************************************/
/*------------------------------------------------------------------------------------------------------------------
Includes
*/
#include "xGUI_Page_Eq.h"
/*------------------------------------------------------------------------------------------------------------------
Macros
*/
#define CHART_POINTS_NUM 256
typedef struct
{
//style list
bool is_style_init;
lv_style_t style_src_main;
lv_style_t style_src_indicator;
lv_obj_t *pWidgetChart;//[2];
lv_chart_series_t *ser1;//[2];
lv_obj_t *pWidgetArc[6];
uint16_t arcPara[6];
}ui_eq_page_manage_t;
/*------------------------------------------------------------------------------------------------------------------
Variables
*/
extern lv_obj_t *pMainWidget;
ui_eq_page_manage_t ui_eq_page_manage;
/*------------------------------------------------------------------------------------------------------------------
Functions
*/
static void eq_style_init(void);
static void refer_chart_cubic_bezier(void);
static void arc_event_callback(lv_event_t * e)
{
lv_obj_t *obj = lv_event_get_target(e);
lv_event_code_t event = lv_event_get_code(e);
uint16_t* puser_data = (uint16_t*)lv_event_get_user_data(e);
char buf[6];
if (event == LV_EVENT_VALUE_CHANGED)
{
*puser_data = lv_arc_get_value(obj);
lv_obj_t *label = lv_obj_get_child(obj, NULL);
lv_snprintf(buf, sizeof(buf), "%d", *puser_data);
lv_label_set_text(label ,buf);
}
refer_chart_cubic_bezier();
}
/*
********************************************************************************************************************
@ Brief : 界面显示初始化
@ Param : None
@ Return : None
@ Author : lyc
@ Date : 2023 - 01 - 29
********************************************************************************************************************
*/
void gui_eq_init(void)
{
int i;
lv_obj_t *label;
char buf[6];
pMainWidget = lv_scr_act();
lv_obj_set_style_bg_color(pMainWidget,lv_palette_darken(LV_PALETTE_GREY,2),0);
eq_style_init();
for(i=0;i<6;i++)
{
ui_eq_page_manage.pWidgetArc[i] = lv_arc_create(pMainWidget);
lv_obj_set_size(ui_eq_page_manage.pWidgetArc[i], 80, 80);
lv_obj_align(ui_eq_page_manage.pWidgetArc[i],LV_ALIGN_TOP_MID, -300 + 120*i, 350);
lv_arc_set_bg_angles(ui_eq_page_manage.pWidgetArc[i],135,45);
lv_arc_set_range(ui_eq_page_manage.pWidgetArc[i], 0, 1024);//256);
lv_arc_set_value(ui_eq_page_manage.pWidgetArc[i], ui_eq_page_manage.arcPara[i]);
lv_obj_add_style(ui_eq_page_manage.pWidgetArc[i],&ui_eq_page_manage.style_src_main,LV_PART_MAIN);
lv_obj_add_style(ui_eq_page_manage.pWidgetArc[i],&ui_eq_page_manage.style_src_indicator,LV_PART_INDICATOR);
lv_obj_remove_style(ui_eq_page_manage.pWidgetArc[i], NULL, LV_PART_KNOB);
label = lv_label_create(ui_eq_page_manage.pWidgetArc[i]);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN | LV_STATE_DEFAULT);
//lv_label_set_text(label, "50");
lv_snprintf(buf, sizeof(buf), "%d", ui_eq_page_manage.arcPara[i]);
lv_label_set_text(label ,buf);
lv_obj_add_event_cb(ui_eq_page_manage.pWidgetArc[i],arc_event_callback,LV_EVENT_VALUE_CHANGED,&ui_eq_page_manage.arcPara[i]);
}
#if 1
ui_eq_page_manage.pWidgetChart = lv_chart_create(pMainWidget);
lv_obj_align(ui_eq_page_manage.pWidgetChart,LV_ALIGN_DEFAULT,0,0);
lv_obj_set_size(ui_eq_page_manage.pWidgetChart,800,300);
lv_obj_set_style_pad_all(ui_eq_page_manage.pWidgetChart, 0, LV_PART_MAIN);
lv_obj_set_style_size(ui_eq_page_manage.pWidgetChart, 0, 0, LV_PART_INDICATOR);
lv_obj_set_style_bg_color(ui_eq_page_manage.pWidgetChart,lv_palette_darken(LV_PALETTE_GREY,1),LV_PART_MAIN);
lv_chart_set_type(ui_eq_page_manage.pWidgetChart, LV_CHART_TYPE_SCATTER);
lv_chart_set_div_line_count(ui_eq_page_manage.pWidgetChart, 11, 5);
ui_eq_page_manage.ser1 = lv_chart_add_series(ui_eq_page_manage.pWidgetChart, lv_palette_main(LV_PALETTE_AMBER), LV_CHART_AXIS_PRIMARY_Y);
lv_chart_set_range(ui_eq_page_manage.pWidgetChart, LV_CHART_AXIS_PRIMARY_Y, 0, 1024);
lv_chart_set_range(ui_eq_page_manage.pWidgetChart, LV_CHART_AXIS_PRIMARY_X, 0, 1024);
lv_chart_set_point_count(ui_eq_page_manage.pWidgetChart, CHART_POINTS_NUM);
#else
for(i=0;i<2;i++)
{
ui_eq_page_manage.pWidgetChart[i] = lv_chart_create(pMainWidget);
lv_obj_align(ui_eq_page_manage.pWidgetChart[i],LV_ALIGN_DEFAULT,400*i,0);
lv_obj_set_size(ui_eq_page_manage.pWidgetChart[i], 400, 300);
lv_obj_set_style_pad_all(ui_eq_page_manage.pWidgetChart[i], 0, LV_PART_MAIN);
lv_obj_set_style_size(ui_eq_page_manage.pWidgetChart[i], 0, 0, LV_PART_INDICATOR);
lv_obj_set_style_bg_color(ui_eq_page_manage.pWidgetChart[i],lv_palette_darken(LV_PALETTE_GREY,1),LV_PART_MAIN);
lv_obj_set_style_border_width(ui_eq_page_manage.pWidgetChart[i], 0, LV_PART_MAIN);
lv_chart_set_type(ui_eq_page_manage.pWidgetChart[i], LV_CHART_TYPE_SCATTER);
ui_eq_page_manage.ser1[i] = lv_chart_add_series(ui_eq_page_manage.pWidgetChart[i], lv_palette_main(LV_PALETTE_AMBER), LV_CHART_AXIS_PRIMARY_Y);
lv_chart_set_range(ui_eq_page_manage.pWidgetChart[i], LV_CHART_AXIS_PRIMARY_Y, 0, 256);
lv_chart_set_range(ui_eq_page_manage.pWidgetChart[i], LV_CHART_AXIS_PRIMARY_X, 0, 256);
lv_chart_set_point_count(ui_eq_page_manage.pWidgetChart[i], 256);
}
#endif
}
/*
********************************************************************************************************************
@ Brief : 风格类型初始化:当前界面所有控件的风格整理
@ Param : None
@ Return : None
@ Author : lyc
@ Date : 2023 - 01 - 29
********************************************************************************************************************
*/
static void eq_style_init(void)
{
if(ui_eq_page_manage.is_style_init == false)
{
lv_style_init(&ui_eq_page_manage.style_src_main);
lv_style_set_arc_color(&ui_eq_page_manage.style_src_main,lv_palette_darken(LV_PALETTE_GREY,3));
lv_style_set_arc_width(&ui_eq_page_manage.style_src_main,8);
lv_style_init(&ui_eq_page_manage.style_src_indicator);
lv_style_set_arc_color(&ui_eq_page_manage.style_src_indicator,lv_palette_lighten(LV_PALETTE_ORANGE,1));
lv_style_set_arc_width(&ui_eq_page_manage.style_src_indicator,8);
ui_eq_page_manage.is_style_init = true;
}
}
/*
********************************************************************************************************************
@ Brief : 贝塞尔二阶函数
@ Param : None
@ Return : None
@ Author : lyc
@ Date : 2023 - 02 - 17
********************************************************************************************************************
*/
#define MAX_TIME (256)
uint32_t lv_bezier2(uint32_t t, uint32_t u0, uint32_t u1, uint32_t u2)
{
uint32_t t_rem = MAX_TIME - t;
uint32_t t_rem2 = (t_rem * t_rem) >> 8;
uint32_t t2 = (t * t) >> 8;
uint32_t v1 = (t_rem2 * u0) >> 8;
uint32_t v2 = (2 * u1 * t * t_rem) >> 16;
uint32_t v3 = (t2 * u2) >> 8;
return v1 + v2 + v3;
}
static void refer_chart_cubic_bezier(void)
{
int i;
int32_t t,step;
#if 1
for(i = 0; i <= CHART_POINTS_NUM; i ++)
{
uint32_t t = i * (1024 / CHART_POINTS_NUM);
step = lv_bezier3(t, ui_eq_page_manage.arcPara[0], ui_eq_page_manage.arcPara[1], ui_eq_page_manage.arcPara[2],ui_eq_page_manage.arcPara[3]);
lv_chart_set_value_by_id2(ui_eq_page_manage.pWidgetChart, ui_eq_page_manage.ser1, i, t, step);
}
#else
for(i = 0; i <= CHART_POINTS_NUM; i ++)
{
step = lv_bezier2(i, ui_eq_page_manage.arcPara[0], ui_eq_page_manage.arcPara[1], ui_eq_page_manage.arcPara[2]);
lv_chart_set_value_by_id2(ui_eq_page_manage.pWidgetChart[0], ui_eq_page_manage.ser1[0], i, i, step);
step = lv_bezier2(i, ui_eq_page_manage.arcPara[2], ui_eq_page_manage.arcPara[3], ui_eq_page_manage.arcPara[4]);
lv_chart_set_value_by_id2(ui_eq_page_manage.pWidgetChart[1], ui_eq_page_manage.ser1[1], i, i, step);
}
#endif
lv_chart_refresh(ui_eq_page_manage.pWidgetChart);
}
/****************************************************END OF FILE*****************************************************/
理解贝塞尔曲线后,可以根据需要搭建多阶算法(阶数越多,曲线拟合更贴切,但算法量越大,对算力较弱的芯片不推荐)