当前位置: 首页 > news >正文

从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(基础图形库实现)

目录

基础图形库的抽象

抽象图形

抽象点

设计我们的抽象

实现我们的抽象

测试

抽象线

设计我们的抽象

实现我们的抽象

绘制垂直的和水平的线

使用Bresenham算法完成任意斜率的绘制

绘制三角形和矩形

矩形

三角形

实现

绘制圆,圆弧和椭圆

继续我们的测试


基础图形库的抽象

历经千辛万苦,我们终于可以开始行动起来,绘制图形了。我们将要绘制线,矩形,圆,椭圆等一系列基础的图形。问我其他的绘制呢?不必着急,我们慢慢来谈。

有没有发现我们现在的谈论越来越高层了?我们现在绘制图像的时候还会关心我用的是硬件IIC或者是软件SPI吗?不会,你甚至可能才意识到我们使用的是OLED!这就是抽象带给我们的好处。我们现在脑子里只有抽象的绘图设备这个概念。它可以绘制点,面。仅此而已。

本篇的代码在:MCU_Libs/OLED/library/Graphic/base at main · Charliechen114514/MCU_Libs (github.com)

抽象图形

抽象点

设计我们的抽象

我们即将迈出我们的第一步,那就是绘制一个点。

typedef uint16_t    PointBaseType;
/*x:  The x-coordinate of the pointy:  The y-coordinate of the pointoperations: An instance of CCGraphic_BaseOperations that stores operations or behaviors related to the point, likely used for drawing or other graphical manipulations.
*/
typedef struct __CCGraphic_Point{PointBaseType                     x;PointBaseType                     y;
}CCGraphic_Point;
​
void CCGraphic_init_point(CCGraphic_Point* point, PointBaseType x, PointBaseType y);
void CCGraphic_draw_point(CCDeviceHandler* handler, CCGraphic_Point* point);

一个点的基本组成,就是给定一个由两个数的组合——X和Y,长度上,笔者为了防止特大设备,使用了PointBaseType隔离了具体的长度大小。

小技巧:

当你发现一个问题很复杂的时候,最好的办法就是隔离!将大问题分解为若干的小问题,以笔者上面遇到的困难为例子。如何保证自己的点可以分布在满足设备宽度的平面上呢?答案是分解问题:点分布在平面上,使用的是对平面属性的PointBaseType上,他只知道自己属于这个类型,就一定不会超越所在的平面,不会出现绘图平面过大导致使用的数据类型发生溢出,至于如何保证不发生溢出呢?那是另一个问题,笔者使用的架构下,不会出现uint16_t不够使用的问题。但是如果的确出现了超大设备,我只需要轻而易举的定义一个HYPER_LARGE_DEVICE的宏,或者是面对资源极端紧张的嵌入式设备,定义一个HYPER_SMALL_DEVICE,就可以让所有的资源占用瞬间缩小一半

#ifdef  HYPER_LARGE_DEVICE
typedef uint32_t    PointBaseType;
#elif defined(HYPER_SMALL_DEVICE)
typedef uint8_t     PointBaseType;
#else
typedef uint16_t    PointBaseType;
#endif

而我其他的代码一行都不用动,轻而易举的完成了迁移。

实现我们的抽象

让我们看看我们的代码多么简洁吧!

#include "Graphic/base/CCGraphic_Point/CCGraphic_Point.h"
#include "Graphic/CCGraphic_device_adapter.h"
​
void CCGraphic_draw_point(CCDeviceHandler* handler, CCGraphic_Point* point)
{handler->operations.set_pixel_device_function(handler, point->x, point->y);
}
​
void CCGraphic_init_point(CCGraphic_Point* point, PointBaseType x, PointBaseType y)
{point->x = x;point->y = y;
}

绘制一个点,就是调用了设备的绘制点的办法。你问我咋绘制的?啥?你需要关心吗?我不说你可能都不知道我是拿的LCD做测试呢(笑),但是,这里我需要严肃提醒的是——不要在关心实时性的绘图设备上这样做,让我们看一看调用链就好了:

CCGraphic_draw_point -> set_pixel_device_function(实际上就是setpixel_device_oled) -> oled_helper_setpixel

也就是说,我们多调用了两次functions来换取对任意设备的抽象。但是我也可以一行代码不改,就可以完全把调用链换成

CCGraphic_draw_point -> set_pixel_device_function(实际上就是setpixel_device_lcd) -> lcd_helper_setpixel

多简单!

测试

现在我们就可以开始测试了

OLED_HARD_IIC_Private_Config pvt_config;
OLED_Handle handle;
CCGraphic_OLED_Config config;
​
void on_test_init_hardiic_oled(CCDeviceHandler* device)
{bind_hardiic_handle(&pvt_config, &hi2c1, 0x78, HAL_MAX_DELAY);config.createType = OLED_HARD_IIC_DRIVER_TYPE;config.related_configs = &pvt_config;register_oled_paintdevice(device, &handle, &config);
}
​
void on_test_draw_points(CCDeviceHandler* handle)
{CCGraphic_Point point;CCGraphic_init_point(&point, 0, 0);for(uint8_t i = 0; i < 20; i++){point.x = i;point.y = i * 2;CCGraphic_draw_point(handle, &point);}handle->operations.update_device_function(handle);
}
​
// at main.c
CCDeviceHandler handler;
on_test_init_hardiic_oled(&handler);
on_test_draw_points(&handler);

不出意外的话不会有任何问题。

抽象线

线的绘制开始有所讲究了,我们需要使用更好的,不涉及浮点数运算的办法来尽可能的回避耗费时间的浮点数运算。这隶属于计算机架构体系的内容,关于ARM,计算浮点数远远比计算整数的开销大(除非使用的是更贵的特化硬件)。现在,让我们开始绘制线线

设计我们的抽象

笔者建议你看到这里了,先自己构思一下如果是你,你如何抽象呢?

笔者先给你看看江科大的代码

void OLED_DrawLine(int16_t X0, int16_t Y0, int16_t X1, int16_t Y1)
{...
}

啥?你问我抽象呢?怎么是实现呢?我只能说——它的函数签名就是抽象咯(笑)。各种处理混在一起,是这样的代码非常难读的一个根本原因。

笔者揭晓我的抽象。

#include "Graphic/base/CCGraphic_Base.h"
#include "Graphic/base/CCGraphic_Point/CCGraphic_Point.h"
​
typedef struct __CCGraphic_Line{CCGraphic_Point p_left;CCGraphic_Point p_right;
}CCGraphic_Line;
​
void CCGraphic_init_line(   CCGraphic_Line* line, CCGraphic_Point pl, CCGraphic_Point pr);
​
void CCGraphic_draw_line(CCDeviceHandler* handler, CCGraphic_Line* line);

一个争论:

这样实现好不好啊?

typedef struct __CCGraphic_Line{CCGraphic_Point* p_left;CCGraphic_Point* p_right;
}CCGraphic_Line;

笔者思考过,事实上,笔者第一代的OLED框架(当然,远远没有现在的那么完善,也远远没有现在的好,甚至还有bug)就是这样实现的。我既然跟上面的实现不一致,那显然,有好处也就有坏处。

我们需要思考的是——我们的对象指针和对象本身表达的含义的区别是什么。关于这个说法,婆说婆有理,公说公有理,笔者这里给出的看法是:

对象本身在结构体中的声明是一种上层抽象对底层对象的强所属权,也就是说,对于每一个整个结构体模板刻出来的结构体对象的成员而言,内部所拥有的点都是独一无二的。换而言之:这就是我的资源,不是借的,更不是偷的!所以现在笔者采用的抽象,更加像是线对点宣誓了主权,这就是线组成的点,没有任何可以商量的余地。

对象指针则是一种弱的引用,表达的是一种借用。上面使用指针占用的抽象,更加像是:借来了两个点,然后用一下这两个点来描述了一下一根直线。用完了对象释放干净了,也就作罢了,但是点本身不会消失。就像我们用一根笔连起来了两个点,组成了一根线,现在我们只是把线擦除了,但是点还在呢!它还可以用来做别的事情。

从内存占用上来看,在ARM32体系上,我们都知道指针的大小是32位,4个字节,所以,我们一个sizeof就能得到使用指针抽象的线也就是8个字节。是一个恒定的大小。对于现在笔者采用的抽象,则是2倍的CCGraphic_Point大小,随着不同的PointBaseType, Line自身的大小也会发生波动,在uint8_t设备上,我们一共是4个字节大小,比指针描述的小,在uint16_t上则是不分伯仲,对于超大设备Line的大小就会膨胀为指针实现的两倍。

但是,另一方面,正如我所说的,这样的资源只是借用,他必须存在于哪个地方,问题来了,你能保证你所使用的点总是有效的吗?

CCGraphic_Line  l;
{CCGraphic_Point tl;CCGraphic_Point br;CCGraphic_init_line(&l, tl, br);
}
// 在这里使用还安全嘛?
CCGraphic_draw_line(&handle, &l);

你也许知道你使用的对象是有效的,但是客户程序员呢?他不知道啊?随后应用层的程序因为非法的内存访问崩溃了(进入了Hard_Fault),他还要幸幸苦苦看你的实现,然后沮丧的调试了一天发现是库作者这个家伙居然只是借用点!最后代来的时间的开销是任何人都无法接受的,这样不确定的风险分明更加的剧烈。

笔者想要说的是:每一个设计都有它的优点和缺点,作为一个合格的程序员,不管是嵌入式程序员,还是架构设计师,都需要明确的表达自己资源的所属权,以及,不要违反“最小惊讶原则”(例子:这个怎么资源突然非法了!为什么库没有帮助我维护???)

实现我们的抽象

规避浮点数运算!这个是我早就说了的。我们需要请出的算法就是Bresenham (montana.edu)算法,这个算法本质上使用的是DDA算法,一种整数微分思维。我们对得到的微分做一次取整,得到的就是整数的点(这是可以接受的,我们没办法在一个LCD或者是OLED上绘制坐标为(1.25, 4.75)的点,不是吗?)

为了化简,我们对绘制直线进行分类讨论

  1. 绘制一条垂直的线

  2. 绘制一条水平的线

  3. 绘制任意斜率的线

void CCGraphic_draw_line(CCDeviceHandler* handler, CCGraphic_Line* line)
{ // test if the verticalif(line->p_left.x == line->p_right.x) return __on_handle_vertical_line(handler, line);   if(line->p_left.y == line->p_right.y)return __on_handle_horizental_line(handler, line);return __pvt_BresenhamMethod_line(handler, line);
}

没想到吧,笔者就用了这几行,完成了这几个事情。好吧,我承认这样有点耍赖了。实际上内部还是颇为复杂,但是,绘制垂直还有水平的线是轻而易举的,试一试?来看看笔者的代码吧!

绘制垂直的和水平的线
/*draw the lines that matches the equal x
*/
static void __on_handle_vertical_line(CCDeviceHandler* handler,CCGraphic_Line* line
)
{PointBaseType max_y = max_uint16(line->p_left.y, line->p_right.y);PointBaseType min_y = min_uint16(line->p_left.y, line->p_right.y);CCGraphic_Point p;p.x = line->p_left.x;for(PointBaseType i = min_y; i <= max_y; i++){p.y = i;CCGraphic_draw_point(handler, &p);}
}
​
static void __on_handle_horizental_line(CCDeviceHandler* handler,CCGraphic_Line* line
)
{PointBaseType max_x = max_uint16(line->p_left.x, line->p_right.x);PointBaseType min_x = min_uint16(line->p_left.x, line->p_right.x);CCGraphic_Point p;p.y = line->p_left.y;for(PointBaseType i = min_x; i <= max_x; i++){p.x = i;CCGraphic_draw_point(handler, &p);}
}

我下面来谈论一下一些要点:

  1. 解释一下max_uint16和min_uint16?

    没啥好解释的啊?这个就是择取大者和小者,有啥好说的呢?

  2. 为什么变量没有像江科大那样一股脑堆在前面呢?

    笔者可以给出充分的原因:我希望变量出现在它该出现的位置,比起来,你也不喜欢看变量一坨屎拉在了函数的前面,下面看实现的时候漫天找这个变量在哪里吧。没那个必要!但是这个需要看情况,如果作者实在不会哪怕一丁点的函数设计,把代码一股脑的堆到了一个函数里,那还不如江科大的变量写法!

  3. 为什么不考虑

    {PointBaseType max_y = max_uint16(line->p_left.y, line->p_right.y);PointBaseType min_y = min_uint16(line->p_left.y, line->p_right.y);for(PointBaseType i = min_y; i <= max_y; i++){CCGraphic_Point p;p.x = line->p_left.x;p.y = i;CCGraphic_draw_point(handler, &p);}
    }

    这个是经典的效率之争。你相信所有的编译器,都会意识到:“哦我的天,这个程序员是一个白痴,p的X坐标永远不会改变,这个白痴为什么要重新赋值一个相同的值max_y - min_y + 1次呢?”嘛? 你不敢!,你永远也不知道使用你的代码的人,在用着怎样的老毕等编译器,他对这样的优化足够迟钝,以至于他对你那可怜的栈来来回回弹弹压压,让你的程序性能被砍到惊呼国骂。你敢打赌使用你库的代码的人,足够的现代嘛?那么,不如让我们的表述更加的明白

    {PointBaseType max_x = max_uint16(line->p_left.x, line->p_right.x);PointBaseType min_x = min_uint16(line->p_left.x, line->p_right.x);CCGraphic_Point p;p.y = line->p_left.y;for(PointBaseType i = min_x; i <= max_x; i++){p.x = i;CCGraphic_draw_point(handler, &p);}
    }

    这样的代码的开销瞬间压到只剩下一次地址解引用和赋值操作了,一下子无论何种编译器,都能生成最为高效的字节码。

  4. 参数设计的时候,对于复杂抽象类型,使用指针还是使用结构体本身传递参数?

    ARM32体系架构有16个寄存器,不同于x86老毕等,传递个结构体最后压内存去了,一些简单的POD类型(我们的Point就是一个简单的POD类型,只有数据没有方法)回直接解析内部的类型是整数,直接传送到寄存器中,将效率提升十几倍,而不用访问内存。这样看,对于一部分最为简单的结构体,直接传递对象本身不是一件特别耗操作的事情,但是,笔者仍然建议:如果你希望这个资源只是被借用一下,或者,表达传递的就是这个对象本身,他在ARM广阔的内存海洋是独一无二的话,使用指针,哪怕他就一个字节大小!

  5. 所以,为什么在函数前面的最前面添加static

    可惜了我们的C语言程序设计表达私有只能使用static办法,这表明,这个函数只能在文件内部访问,实际上的函数签名会被独特标记,导致外部生成的签名无法对应于实际上被static修饰的函数,这也就意味着无法通过编译!他没办法认识这个被static修饰的函数。至于其他乱七八糟的什么重名问题,我负责的告诉你,不要指望所有编译器都会正确的反应你的UB行为,不然,你就会在“编译了半天发现被这个问题绊了一跤”和“这个程序的行为怎么这么诡异啊?不是跳转道我想要的函数”中二选一了,反正代价是你的一天被你的UB行为坑害(笑)

使用Bresenham算法完成任意斜率的绘制
// Bresenham's Line Algorithm, designed to avoid floating point calculations
// References: https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf
// https://www.bilibili.com/video/BV1364y1d7Lo
void __pvt_BresenhamMethod_line(CCDeviceHandler* handler, CCGraphic_Line* line)
{
#define __pvt_fast_draw_point(X, Y) \do { \p.x = X; \p.y = Y; \CCGraphic_draw_point(handler, &p); \} while(0)
​// Define initial points for the line: p_left and p_right represent the endpointsint16_t startX = line->p_left.x;int16_t startY = line->p_left.y;int16_t endX = line->p_right.x;int16_t endY = line->p_right.y;
​// Flags to indicate transformations of coordinatesuint8_t isYInverted = 0, isXYInverted = 0;{// If the start point's X coordinate is greater than the end point's X, swap the pointsif (startX > endX) {// Swap the X and Y coordinates for the start and end pointsswap_int16(&startX, &endX);swap_int16(&startY, &endY);}
​// If the start point's Y coordinate is greater than the end point's Y, invert the Y coordinatesif (startY > endY) {// Invert Y coordinates to make the line direction consistent in the first quadrantstartY = -startY;endY = -endY;// Set the flag indicating Y coordinates were invertedisYInverted = 1;}
​// If the line's slope (dy/dx) is greater than 1, swap X and Y coordinates for a shallower slopeif (endY - startY > endX - startX) {// Swap X and Y coordinates for both pointsswap_int16(&startX, &startY);swap_int16(&endX, &endY);// Set the flag indicating both X and Y coordinates were swappedisXYInverted = 1;}
​// Calculate differences (dx, dy) and the decision variables for Bresenham's algorithmconst int16_t dx = endX - startX;const int16_t dy = endY - startY;const int16_t incrE = 2 * dy;  // Increment for eastward movementconst int16_t incrNE = 2 * (dy - dx);  // Increment for northeastward movement
​int16_t decision = 2 * dy - dx;  // Initial decision variableint16_t x = startX;  // Starting X coordinateint16_t y = startY;  // Starting Y coordinate
​// Draw the starting point and handle coordinate transformations based on flagsCCGraphic_Point p;if (isYInverted && isXYInverted) {__pvt_fast_draw_point(y, -x);} else if (isYInverted) {__pvt_fast_draw_point(x, -y);} else if (isXYInverted) {__pvt_fast_draw_point(y, x);} else {__pvt_fast_draw_point(x, y);}
​// Iterate through the X-axis to draw the rest of the linewhile (x < endX) {x++;  // Increment X coordinateif (decision < 0) {decision += incrE;  // Move eastward if decision variable is negative} else {y++;  // Move northeastward if decision variable is positive or zerodecision += incrNE;}
​// Draw each point along the line with coordinate transformation as neededif (isYInverted && isXYInverted) {__pvt_fast_draw_point(y, -x);} else if (isYInverted) {__pvt_fast_draw_point(x, -y);} else if (isXYInverted) {__pvt_fast_draw_point(y, x);} else {__pvt_fast_draw_point(x, y);}}}
#undef __pvt_fast_draw_point
}

好长一大串,先不必着急,我一步步慢慢说。实际上,这个算法除了使用DDA以外,还用了化未知为已知的办法。我的意思是:

        // If the start point's X coordinate is greater than the end point's X, swap the pointsif (startX > endX) {// Swap the X and Y coordinates for the start and end pointsswap_int16(&startX, &endX);swap_int16(&startY, &endY);}
​// If the start point's Y coordinate is greater than the end point's Y, invert the Y coordinatesif (startY > endY) {// Invert Y coordinates to make the line direction consistent in the first quadrantstartY = -startY;endY = -endY;// Set the flag indicating Y coordinates were invertedisYInverted = 1;}

首先,确保我们的线总是向正的,斜率总是大于0

   
    // If the line's slope (dy/dx) is greater than 1, swap X and Y coordinates for a shallower slopeif (endY - startY > endX - startX) {// Swap X and Y coordinates for both pointsswap_int16(&startX, &startY);swap_int16(&endX, &endY);// Set the flag indicating both X and Y coordinates were swappedisXYInverted = 1;}

上面则是在斜率大于1的基础上,将变换映射到介于0 < k < 1的范围上。

最后,使用核心算法直接绘制

        // Calculate differences (dx, dy) and the decision variables for Bresenham's algorithmconst int16_t dx = endX - startX;const int16_t dy = endY - startY;const int16_t incrE = 2 * dy;  // Increment for eastward movementconst int16_t incrNE = 2 * (dy - dx);  // Increment for northeastward movement
​int16_t decision = 2 * dy - dx;  // Initial decision variableint16_t x = startX;  // Starting X coordinateint16_t y = startY;  // Starting Y coordinate
​// Draw the starting point and handle coordinate transformations based on flagsCCGraphic_Point p;if (isYInverted && isXYInverted) {__pvt_fast_draw_point(y, -x);} else if (isYInverted) {__pvt_fast_draw_point(x, -y);} else if (isXYInverted) {__pvt_fast_draw_point(y, x);} else {__pvt_fast_draw_point(x, y);}
​// Iterate through the X-axis to draw the rest of the linewhile (x < endX) {x++;  // Increment X coordinateif (decision < 0) {decision += incrE;  // Move eastward if decision variable is negative} else {y++;  // Move northeastward if decision variable is positive or zerodecision += incrNE;}
​// Draw each point along the line with coordinate transformation as neededif (isYInverted && isXYInverted) {__pvt_fast_draw_point(y, -x);} else if (isYInverted) {__pvt_fast_draw_point(x, -y);} else if (isXYInverted) {__pvt_fast_draw_point(y, x);    // 对角对称,互换XY即可变换} else {__pvt_fast_draw_point(x, y);}}}

这个代码就是直接翻译了我给的PDF的算法,下面来聊一聊算法之外的:

  1. 使用宏来化简我们的工作

    #define __pvt_fast_draw_point(X, Y) \do { \p.x = X; \p.y = Y; \CCGraphic_draw_point(handler, &p); \} while(0)

    这个是一个简单的封装宏,为什么使用do..while请参考笔者之前的博客(协议篇)

    C没有constexpr,没有模板,有的时候会显得十分贫瘠,所以,我们只好忍一下,使用宏完成重复的,0开销的工作。

绘制三角形和矩形

矩形
#ifndef CCGraphic_Rectangle_H
#define CCGraphic_Rectangle_H
​
#include "Graphic/base/CCGraphic_Base.h"
#include "Graphic/base/CCGraphic_Point/CCGraphic_Point.h"
​
typedef struct __CCGraphic_Rectangle{CCGraphic_Point         top_left;CCGraphic_Point         bottom_right;
}CCGraphic_Rectangle;
​
void CCGraphic_init_rectangle(CCGraphic_Rectangle* rect, CCGraphic_Point tl, CCGraphic_Point br);
​
void CCGraphic_draw_rectangle(CCDeviceHandler* handler, CCGraphic_Rectangle* rect);
​
void CCGraphic_drawfilled_rectangle(CCDeviceHandler* handler, CCGraphic_Rectangle* rect);
​
#endif
三角形
#ifndef CCGraphic_Triangle_H
#define CCGraphic_Triangle_H
​
#include "Graphic/base/CCGraphic_Base.h"
#include "Graphic/base/CCGraphic_Point/CCGraphic_Point.h"
​
typedef struct __CCGraphic_Triangle
{CCGraphic_Point     p1;CCGraphic_Point     p2;CCGraphic_Point     p3;
}CCGraphic_Triangle;
​
​
void CCGraphic_init_triangle(CCGraphic_Triangle* triangle, CCGraphic_Point     p1,CCGraphic_Point     p2,CCGraphic_Point     p3
);
​
void CCGraphic_draw_triangle(CCDeviceHandler*    handle,CCGraphic_Triangle* triangle
);
​
void CCGraphic_drawfilled_triangle(CCDeviceHandler*    handle,CCGraphic_Triangle* triangle
);
​
#endif
实现

我们还是使用Bresenham算法和Franklin算法完成我们对三角形和矩形的绘制

#include "Graphic/base/CCGraphic_Triangle/CCGraphic_Triangle.h"
#include "Graphic/base/CCGraphic_Line/CCGraphic_Line.h"
#include "Graphic/CCGraphic_device_adapter.h"
#include "Graphic/common/CCGraphic_Utils.h"
​
void CCGraphic_init_triangle(CCGraphic_Triangle* triangle, CCGraphic_Point     p1,CCGraphic_Point     p2,CCGraphic_Point     p3
)
{triangle->p1 = p1;triangle->p2 = p2;triangle->p3 = p3;
}
​
void CCGraphic_draw_triangle(CCDeviceHandler*    handle,CCGraphic_Triangle* triangle
)
{CCGraphic_Line  line;CCGraphic_init_line(&line, triangle->p1, triangle->p2);CCGraphic_draw_line(handle, &line);handle->operations.update_device_function(handle);CCGraphic_init_line(&line, triangle->p2, triangle->p3);CCGraphic_draw_line(handle, &line);handle->operations.update_device_function(handle);CCGraphic_init_line(&line, triangle->p1, triangle->p3);CCGraphic_draw_line(handle, &line);
}
​
static uint8_t __pvt_is_in_triangle(int16_t* triangles_x,int16_t* triangles_y,CCGraphic_Point* p)
{uint8_t is_in = 0;/*此算法由W. Randolph Franklin提出*//*参考链接:https://wrfranklin.org/Research/Short_Notes/pnpoly.html*/for (uint8_t i = 0, j = 2; i < 3; j = i++){if (((triangles_y[i] > p->y) != (triangles_y[j] > p->y)) &&(p->x < (triangles_x[j] - triangles_x[i]) * (p->y - triangles_y[i]) / (triangles_y[j] - triangles_y[i]) + triangles_x[i])){is_in = !is_in;}}return is_in;
}
​
void CCGraphic_drawfilled_triangle(CCDeviceHandler*    handle,CCGraphic_Triangle* triangle
)
{int16_t triangles_x[] = {triangle->p1.x, triangle->p2.x, triangle->p3.x};
​int16_t triangles_y[] = {triangle->p1.y, triangle->p2.y, triangle->p3.y};
​int16_t minX = find_int16min(triangles_x, 3);int16_t minY = find_int16min(triangles_y, 3);
​int16_t maxX = find_int16max(triangles_x, 3);int16_t maxY = find_int16max(triangles_y, 3);CCGraphic_Point p;p.x = minX;p.y = minY;for(int16_t i = minX; i < maxX; i++){for(int16_t j = minY; j < maxY; j++){p.x = i;p.y = j;if(__pvt_is_in_triangle(triangles_x, triangles_y, &p)){CCGraphic_draw_point(handle, &p);}}}
​
}
​
#include "Graphic/base/CCGraphic_Rectangle/CCGraphic_Rectangle.h"
#include "Graphic/base/CCGraphic_Line/CCGraphic_Line.h"
​
void CCGraphic_init_rectangle(CCGraphic_Rectangle* rect, CCGraphic_Point tl, CCGraphic_Point br)
{rect->top_left = tl;rect->bottom_right = br;
}
​
void CCGraphic_draw_rectangle(CCDeviceHandler* handler, CCGraphic_Rectangle* rect)
{CCGraphic_Line l;CCGraphic_Point tmp;
​// draw top, set tmp as the top_righttmp.x = rect->bottom_right.x;tmp.y = rect->top_left.y;CCGraphic_init_line(&l, rect->top_left, tmp);CCGraphic_draw_line(handler, &l);
​// draw rightCCGraphic_init_line(&l, tmp, rect->bottom_right);CCGraphic_draw_line(handler, &l);    
​// draw lefttmp.x = rect->top_left.x;tmp.y = rect->bottom_right.y;CCGraphic_init_line(&l, rect->top_left, tmp);CCGraphic_draw_line(handler, &l);      
​// draw bottomCCGraphic_init_line(&l,tmp, rect->bottom_right);CCGraphic_draw_line(handler, &l);      
}
​
​
void CCGraphic_drawfilled_rectangle(CCDeviceHandler* handler, CCGraphic_Rectangle* rect)
{CCGraphic_Point p;for(PointBaseType iterate_x = rect->top_left.x; iterate_x <= rect->bottom_right.x; iterate_x++){p.x = iterate_x;for(PointBaseType iterate_y = rect->top_left.y; iterate_y <= rect->bottom_right.y; iterate_y++){p.y = iterate_y;CCGraphic_draw_point(handler, &p);}        }
}

小问题:提示,矩形的填充绘制是可以优化,你认为应该如何优化呢?(提示:我们是不是用错了device的功能了?)(可以在评论区回答的)

绘制圆,圆弧和椭圆

没有什么特殊的,笔者出于一些人上不去github,先把代码放到这里。

MCU_Libs/OLED/library/Graphic/base at main · Charliechen114514/MCU_Libs (github.com)

#ifndef CCGraphic_Arc_H
#define CCGraphic_Arc_H
#include "Graphic/base/CCGraphic_Base.h"
#include "Graphic/base/CCGraphic_Point/CCGraphic_Point.h"
​
typedef struct __CCGraphic_Arc{CCGraphic_Point     center;PointBaseType       radius;int16_t             start_degree;int16_t             end_degree;
}CCGraphic_Arc;
​
void CCGraphic_init_CCGraphic_Arc(CCGraphic_Arc*      handle,CCGraphic_Point     center,PointBaseType       radius,int16_t             start_degree,int16_t             end_degree  
);
​
void CCGraphic_draw_arc(CCDeviceHandler* handler,CCGraphic_Arc* handle
);
​
void CCGraphic_drawfilled_arc(CCDeviceHandler* handler,CCGraphic_Arc* handle
);
​
#endif
​
#ifndef __CCGraphic_Circle_H
#define __CCGraphic_Circle_H
#include "Graphic/base/CCGraphic_Base.h"
#include "Graphic/base/CCGraphic_Point/CCGraphic_Point.h"
​
typedef struct __CCGraphic_Circle
{CCGraphic_Point             center;PointBaseType               radius;
}CCGraphic_Circle;
​
void CCGraphic_init_circle(CCGraphic_Circle* circle, CCGraphic_Point c, uint8_t radius);
void CCGraphic_draw_circle(CCDeviceHandler* handler, CCGraphic_Circle* circle);
void CCGraphic_drawfilled_circle(CCDeviceHandler* handler, CCGraphic_Circle* circle);
#endif
​
#ifndef CCGraphic_Ellipse_H
#define CCGraphic_Ellipse_H
#include "Graphic/base/CCGraphic_Base.h"
#include "Graphic/base/CCGraphic_Point/CCGraphic_Point.h"
​
typedef struct __CCGraphic_Ellipse{CCGraphic_Point                 center;PointBaseType                   X_Radius;PointBaseType                   Y_Radius;  
}CCGraphic_Ellipse;
​
void CCGraphic_init_ellipse(CCGraphic_Ellipse*          handle, CCGraphic_Point             center,PointBaseType               X_Radius,PointBaseType               Y_Radius 
);
​
void CCGraphic_draw_ellipse(CCDeviceHandler* handler,CCGraphic_Ellipse* ellipse
);
​
void CCGraphic_drawfilled_ellipse(CCDeviceHandler* handler,CCGraphic_Ellipse* ellipse
);
​
#endif

实现如下

#include "Graphic/base/CCGraphic_Arc/CCGraphic_Arc.h"
#include <math.h>
​
void CCGraphic_init_CCGraphic_Arc(CCGraphic_Arc*      handle,CCGraphic_Point     center,PointBaseType       radius,int16_t             start_degree,int16_t             end_degree  
)
{handle->center          = center;handle->end_degree      = end_degree;handle->start_degree    = start_degree;handle->radius          = radius;
}
​
static uint8_t __pvt_is_in_angle(int16_t x, int16_t y, int16_t start, int16_t end)
{int16_t point_angle = (atan2(y, x) / 3.14 * 180);// 笔者的一个更加清晰的写法// if (start < end) //起始角度小于终止角度的情况// {//  /*如果指定角度在起始终止角度之间,则判定指定点在指定角度*///  if (point_angle >= start && point_angle <= end)//  {//      return 1;//  }// }// else         //起始角度大于于终止角度的情况// {//  /*如果指定角度大于起始角度或者小于终止角度,则判定指定点在指定角度*///  if (point_angle >= start || point_angle <= end)//  {//      return 1;//  }// }// return 0;    
​return start < end ?(start < point_angle && point_angle < end):(start > point_angle || point_angle > end);
}
​
#define DRAW_OFFSET_POINT(offsetx, offsety) \do{\point.x = handle->center.x + (offsetx);\point.y = handle->center.y + (offsety);\CCGraphic_draw_point(handler, &point);\}while(0)
​
#define DRAW_IF_IN(offsetx, offsety) \do{\if (__pvt_is_in_angle((offsetx), (offsety), start_angle, end_angle))    {\DRAW_OFFSET_POINT(offsetx, offsety);\}\}while(0)   
​
void CCGraphic_draw_arc(CCDeviceHandler* handler,CCGraphic_Arc* handle
)
{/*此函数借用Bresenham算法画圆的方法*/   int16_t x = 0;int16_t y = handle->radius;int16_t d = 1 - y;
​CCGraphic_Point point;const int16_t start_angle = handle->start_degree;const int16_t end_angle = handle->end_degree;/*在画圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/DRAW_IF_IN(x, y);DRAW_IF_IN(-x, -y);DRAW_IF_IN(y, x);DRAW_IF_IN(-y, -x);while (x < y)       //遍历X轴的每个点{x ++;if (d < 0)      //下一个点在当前点东方{d += 2 * x + 1;}else            //下一个点在当前点东南方{y --;d += 2 * (x - y) + 1;}/*在画圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/DRAW_IF_IN(x, y);DRAW_IF_IN(y, x);DRAW_IF_IN(-x, -y);DRAW_IF_IN(-y, -x);DRAW_IF_IN(x, -y);DRAW_IF_IN(y, -x);DRAW_IF_IN(-x, y);DRAW_IF_IN(-y, x);}
}
​
void CCGraphic_drawfilled_arc(CCDeviceHandler* handler,CCGraphic_Arc* handle
)
{/*此函数借用Bresenham算法画圆的方法*/   int16_t x = 0;int16_t y = handle->radius;int16_t d = 1 - y;
​CCGraphic_Point point;const int16_t start_angle = handle->start_degree;const int16_t end_angle = handle->end_degree;point.x = x;point.y = y;/*在画圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/DRAW_IF_IN(x, y);DRAW_IF_IN(-x, -y);DRAW_IF_IN(y, x);DRAW_IF_IN(-y, -x);
​/*遍历起始点Y坐标*/for (int16_t j = -y; j < y; j ++){/*在填充圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/DRAW_IF_IN(0, j);}while (x < y)       //遍历X轴的每个点{x ++;if (d < 0)      //下一个点在当前点东方{d += 2 * x + 1;}else            //下一个点在当前点东南方{y --;d += 2 * (x - y) + 1;}/*在画圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/DRAW_IF_IN(x, y);DRAW_IF_IN(y, x);DRAW_IF_IN(-x, -y);DRAW_IF_IN(-y, -x);DRAW_IF_IN(x, -y);DRAW_IF_IN(y, -x);DRAW_IF_IN(-x, y);DRAW_IF_IN(-y, x);
​/*遍历中间部分*/for (int16_t j = -y; j < y; j ++){/*在填充圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/DRAW_IF_IN(x, j);DRAW_IF_IN(-x, j);}/*遍历两侧部分*/for (int16_t j = -x; j < x; j ++){/*在填充圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/DRAW_IF_IN(y, j);DRAW_IF_IN(-y, j);}}
}
​
#undef DRAW_OFFSET_POINT
#undef DRAW_IF_IN
​
#include "Graphic/base/CCGraphic_Ellipse/CCGraphic_Ellipse.h"
​
void CCGraphic_init_ellipse(CCGraphic_Ellipse*          handle, CCGraphic_Point             center,PointBaseType               X_Radius,PointBaseType               Y_Radius 
)
{handle->center = center;handle->X_Radius = X_Radius;handle->Y_Radius = Y_Radius;
}
​
#define DRAW_OFFSET_POINT(offsetx, offsety) \do{\point.x = ellipse->center.x + (offsetx);\point.y = ellipse->center.y + (offsety);\CCGraphic_draw_point(handler, &point);\}while(0)
​
#define SQUARE(X) ((X) * (X))
​
void CCGraphic_draw_ellipse(CCDeviceHandler* handler,CCGraphic_Ellipse* ellipse
)
{const int16_t x_radius = ellipse->X_Radius;const int16_t y_radius = ellipse->Y_Radius;
​// Bresenham's Ellipse Algorithm to avoid costly floating point calculations// Reference: https://blog.csdn.net/myf_666/article/details/128167392
​int16_t x = 0;int16_t y = y_radius;const int16_t y_radius_square = SQUARE(y_radius);const int16_t x_radius_square = SQUARE(x_radius);
​// Initial decision variable for the first region of the ellipsefloat d1 = y_radius_square + x_radius_square * (-y_radius + 0.5);
​// Draw initial points on the ellipse (4 points due to symmetry)CCGraphic_Point point;DRAW_OFFSET_POINT(x, y);DRAW_OFFSET_POINT(-x, -y);DRAW_OFFSET_POINT(-x, y);DRAW_OFFSET_POINT(x, -y);
​// Draw the middle part of the ellipse (first region)while (y_radius_square * (x + 1) < x_radius_square * (y - 0.5)) {if (d1 <= 0) {  // Next point is to the east of the current pointd1 += y_radius_square * (2 * x + 3);} else {  // Next point is southeast of the current pointd1 += y_radius_square * (2 * x + 3) + x_radius_square * (-2 * y + 2);y--;}x++;
​// Draw ellipse arc for each point in the current regionDRAW_OFFSET_POINT(x, y);DRAW_OFFSET_POINT(-x, -y);DRAW_OFFSET_POINT(-x, y);DRAW_OFFSET_POINT(x, -y);}
​// Draw the two sides of the ellipse (second region)float d2 = SQUARE(y_radius * (x + 0.5)) + SQUARE(x_radius * (y - 1)) - x_radius_square * y_radius_square;
​while (y > 0) {if (d2 <= 0) {  // Next point is to the east of the current pointd2 += y_radius_square * (2 * x + 2) + x_radius_square * (-2 * y + 3);x++;} else {  // Next point is southeast of the current pointd2 += x_radius_square * (-2 * y + 3);}y--;
​// Draw ellipse arc for each point on the sidesDRAW_OFFSET_POINT(x, y);DRAW_OFFSET_POINT(-x, -y);DRAW_OFFSET_POINT(-x, y);DRAW_OFFSET_POINT(x, -y);}
}
​
void CCGraphic_drawfilled_ellipse(CCDeviceHandler* handler,CCGraphic_Ellipse* ellipse
)
{const int16_t x_radius = ellipse->X_Radius;const int16_t y_radius = ellipse->Y_Radius;
​// Bresenham's Ellipse Algorithm to avoid costly floating point calculations// Reference: https://blog.csdn.net/myf_666/article/details/128167392
​int16_t x = 0;int16_t y = y_radius;const int16_t y_radius_square = SQUARE(y_radius);const int16_t x_radius_square = SQUARE(x_radius);
​// Initial decision variable for the first region of the ellipsefloat d1 = y_radius_square + x_radius_square * (-y_radius + 0.5);CCGraphic_Point point;// Fill the ellipse by drawing vertical lines in the specified range (filled area)for (int16_t j = -y; j < y; j++) {// Draw vertical lines to fill the area of the ellipseDRAW_OFFSET_POINT(0, j);DRAW_OFFSET_POINT(0, j);}
​// Draw initial points on the ellipse (4 points due to symmetry)DRAW_OFFSET_POINT(x, y);DRAW_OFFSET_POINT(-x, -y);DRAW_OFFSET_POINT(-x, y);DRAW_OFFSET_POINT(x, -y);
​// Draw the middle part of the ellipse (first region)while (y_radius_square * (x + 1) < x_radius_square * (y - 0.5)) {if (d1 <= 0) {  // Next point is to the east of the current pointd1 += y_radius_square * (2 * x + 3);} else {  // Next point is southeast of the current pointd1 += y_radius_square * (2 * x + 3) + x_radius_square * (-2 * y + 2);y--;}x++;
​// Fill the ellipse by drawing vertical lines in the current rangefor (int16_t j = -y; j < y; j++) {DRAW_OFFSET_POINT(x, j);DRAW_OFFSET_POINT(-x, j);}
​// Draw ellipse arc for each point in the current regionDRAW_OFFSET_POINT(x, y);DRAW_OFFSET_POINT(-x, -y);DRAW_OFFSET_POINT(-x, y);DRAW_OFFSET_POINT(x, -y);}
​// Draw the two sides of the ellipse (second region)float d2 = SQUARE(y_radius * (x + 0.5)) + SQUARE(x_radius * (y - 1)) - x_radius_square * y_radius_square;
​while (y > 0) {if (d2 <= 0) {  // Next point is to the east of the current pointd2 += y_radius_square * (2 * x + 2) + x_radius_square * (-2 * y + 3);x++;} else {  // Next point is southeast of the current pointd2 += x_radius_square * (-2 * y + 3);}y--;
​// Fill the ellipse by drawing vertical lines in the current rangefor (int16_t j = -y; j < y; j++) {DRAW_OFFSET_POINT(x, j);DRAW_OFFSET_POINT(-x, j);}
​// Draw ellipse arc for each point on the sidesDRAW_OFFSET_POINT(x, y);DRAW_OFFSET_POINT(-x, -y);DRAW_OFFSET_POINT(-x, y);DRAW_OFFSET_POINT(x, -y);}
}
​
#undef DRAW_OFFSET_POINT
#undef SQUARE
​
#include "Graphic/base/CCGraphic_Circle/CCGraphic_Circle.h"
#include "Graphic/CCGraphic_device_adapter.h"
#include "Graphic/common/CCGraphic_Utils.h"
​
void CCGraphic_init_circle(CCGraphic_Circle* circle, CCGraphic_Point c, uint8_t radius)
{circle->center = c;circle->radius = radius;
}
​
#define DRAW_OFFSET_POINT(point, offsetx, offsety) \do { \point.x = circle->center.x + (offsetx); \point.y = circle->center.y + (offsety); \CCGraphic_draw_point(handler, &point);}while(0)
​
void CCGraphic_draw_circle(CCDeviceHandler* handler, CCGraphic_Circle* circle)
{/*参考文档:https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf*//*参考教程:https://www.bilibili.com/video/BV1VM4y1u7wJ*/CCGraphic_Point p;int16_t d = 1 - circle->radius;int16_t x = 0;int16_t y = circle->radius;
​DRAW_OFFSET_POINT(p, x, y);DRAW_OFFSET_POINT(p, -x, -y);DRAW_OFFSET_POINT(p, y, x);DRAW_OFFSET_POINT(p, -y, -x);
​while(x < y){x++;if(d < 0){ d += 2 * x + 1;}else {y--; d += 2 * (x - y) + 1;}DRAW_OFFSET_POINT(p, x, y);DRAW_OFFSET_POINT(p, y, x);DRAW_OFFSET_POINT(p, -x, -y);DRAW_OFFSET_POINT(p, -y, -x);DRAW_OFFSET_POINT(p, x, -y);DRAW_OFFSET_POINT(p, y, -x);DRAW_OFFSET_POINT(p, -x, y);DRAW_OFFSET_POINT(p, -y, x);            }
}
​
void CCGraphic_drawfilled_circle(CCDeviceHandler* handler, CCGraphic_Circle* circle)
{CCGraphic_Point p;int16_t d = 1 - circle->radius;int16_t x = 0;int16_t y = circle->radius;
​DRAW_OFFSET_POINT(p, x, y);DRAW_OFFSET_POINT(p, -x, -y);DRAW_OFFSET_POINT(p, y, x);DRAW_OFFSET_POINT(p, -y, -x);
​for(int16_t i = -y; i < y; i++)DRAW_OFFSET_POINT(p, 0, i);
​while(x < y){x++;if(d < 0){ d += 2 * x + 1;}else {y--; d += 2 * (x - y) + 1;}DRAW_OFFSET_POINT(p, x, y);DRAW_OFFSET_POINT(p, y, x);DRAW_OFFSET_POINT(p, -x, -y);DRAW_OFFSET_POINT(p, -y, -x);DRAW_OFFSET_POINT(p, x, -y);DRAW_OFFSET_POINT(p, y, -x);DRAW_OFFSET_POINT(p, -x, y);DRAW_OFFSET_POINT(p, -y, x);   for(int16_t i = -y; i < y; i++){DRAW_OFFSET_POINT(p, x, i);DRAW_OFFSET_POINT(p, -x, i);  }for(int16_t i = -x; i < x; i++){DRAW_OFFSET_POINT(p, y, i);DRAW_OFFSET_POINT(p, -y, i);  }              }    
}
​
#undef DRAW_OFFSET_POINT

现在我们可以上测试了

继续我们的测试

#include "Test/GraphicTest/graphic_test.h"
#include "Graphic/base/CCGraphic_Point/CCGraphic_Point.h"
#include "Graphic/base/CCGraphic_Line/CCGraphic_Line.h"
#include "Graphic/base/CCGraphic_Circle/CCGraphic_Circle.h"
#include "Graphic/base/CCGraphic_Rectangle/CCGraphic_Rectangle.h"
#include "Graphic/base/CCGraphic_Triangle/CCGraphic_Triangle.h"
#include "Graphic/base/CCGraphic_Ellipse/CCGraphic_Ellipse.h"
#include "Graphic/base/CCGraphic_Arc/CCGraphic_Arc.h"
​
void on_test_draw_points(CCDeviceHandler* handle)
{CCGraphic_Point point;CCGraphic_init_point(&point, 0, 0);for(uint8_t i = 0; i < 20; i++){point.x = i;point.y = i * 2;CCGraphic_draw_point(handle, &point);}handle->operations.update_device_function(handle);
}
​
void on_test_draw_line(CCDeviceHandler* handle)
{CCGraphic_Line  l;CCGraphic_Point pleft;CCGraphic_Point pright;// try verticalpleft.x     = 5;pleft.y     = 0;pright.x    = pleft.x;pright.y    = 63;
​CCGraphic_init_line(&l, pleft, pright);CCGraphic_draw_line(handle, &l);
​// try horizontalpleft.x     = 0;pleft.y     = 5;pright.x    = 120;pright.y    = pleft.y;
​CCGraphic_init_line(&l, pleft, pright);CCGraphic_draw_line(handle, &l);
​// try differentpleft.x     = 0;pleft.y     = 10;pright.x    = 105;pright.y    = 63;
​CCGraphic_init_line(&l, pleft, pright);CCGraphic_draw_line(handle, &l);handle->operations.update_device_function(handle);
}
​
void on_test_draw_circle(CCDeviceHandler* handle)
{CCGraphic_Circle c;CCGraphic_Point p;p.x = 64;p.y = 32;CCGraphic_init_circle(&c, p, 10);CCGraphic_drawfilled_circle(handle, &c);
​p.x = 10;p.y = 32;CCGraphic_init_circle(&c, p, 5);CCGraphic_draw_circle(handle, &c);handle->operations.update_device_function(handle);
}
​
void on_test_draw_rectangle(CCDeviceHandler* handle)
{CCGraphic_Rectangle rect;CCGraphic_Point     tl;CCGraphic_Point     br;
​tl.x = 5;tl.y = 5;
​br.x = 20;br.y = 20;
​CCGraphic_init_rectangle(&rect, tl, br);CCGraphic_draw_rectangle(handle, &rect);
​tl.x = 21;tl.y = 21;
​br.x = 50;br.y = 50;    CCGraphic_init_rectangle(&rect, tl, br);CCGraphic_drawfilled_rectangle(handle, &rect);handle->operations.update_device_function(handle);
}
​
void on_test_draw_triangle(CCDeviceHandler* handle)
{CCGraphic_Triangle  triangle;CCGraphic_Point     p1;CCGraphic_Point     p2;CCGraphic_Point     p3;
​p1.x = 10;p1.y = 10;
​p2.x = 15;p2.y = 5;
​p3.x = 80;p3.y = 40;
​CCGraphic_init_triangle(&triangle, p1, p3, p2);CCGraphic_drawfilled_triangle(handle, &triangle);handle->operations.update_device_function(handle);
}
​
void on_test_draw_ellipse(CCDeviceHandler* handle)
{CCGraphic_Ellipse ellipse;CCGraphic_Point p;p.x = 20;p.y = 32;
​CCGraphic_init_ellipse(&ellipse, p, 10, 30);CCGraphic_draw_ellipse(handle, &ellipse);
​p.x = 80;p.y = 32;CCGraphic_init_ellipse(&ellipse, p, 40, 30);CCGraphic_drawfilled_ellipse(handle, &ellipse);handle->operations.update_device_function(handle);
}
​
void on_test_draw_arc(CCDeviceHandler* handle)
{CCGraphic_Arc arc;CCGraphic_Point p;p.x = 64;p.y = 32;CCGraphic_init_CCGraphic_Arc(&arc, p, 40, -20, 40);CCGraphic_draw_arc(handle, &arc);handle->operations.update_device_function(handle);
}

在main.c中就可以这样调用

    on_test_draw_points(handler);HAL_Delay(1000);on_test_draw_line(handler);HAL_Delay(1000);on_test_draw_circle(handler);HAL_Delay(1000);on_test_draw_rectangle(handler);HAL_Delay(1000);on_test_draw_triangle(handler);HAL_Delay(1000);on_test_draw_ellipse(handler);HAL_Delay(1000);on_test_draw_arc(handler);

完整测试视频

目录导览

总览

协议层封装

OLED设备封装

绘图设备抽象

基础图形库封装

基础组件实现

动态菜单组件实现

相关文章:

从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(基础图形库实现)

目录 基础图形库的抽象 抽象图形 抽象点 设计我们的抽象 实现我们的抽象 测试 抽象线 设计我们的抽象 实现我们的抽象 绘制垂直的和水平的线 使用Bresenham算法完成任意斜率的绘制 绘制三角形和矩形 矩形 三角形 实现 绘制圆&#xff0c;圆弧和椭圆 继续我们的…...

创新创业计划书|建筑垃圾资源化回收

目录 第1部分 公司概况........................................................................ 1 第2部分 产品/服务...................................................................... 3 第3部分 研究与开发.................................................…...

反射、枚举以及lambda表达式

一.反射 1.概念&#xff1a;Java的反射&#xff08;reflection&#xff09;机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意方法和属性&#xff0c;既然能拿到那么&am…...

ROS应用之IMU碰撞检测与接触事件识别

前言 碰撞检测与接触事件识别是机器人与环境交互中的重要任务&#xff0c;尤其是在复杂的动态环境中。IMU&#xff08;惯性测量单元&#xff09;作为一种高频率、低延迟的传感器&#xff0c;因其能够检测加速度、角速度等动态变化而成为实现碰撞检测的核心手段之一。结合先进的…...

docker安装MySQL8:docker离线安装MySQL、docker在线安装MySQL、MySQL镜像下载、MySQL配置、MySQL命令

一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull mysql:8.0.41 2、离线包下载 两种方式&#xff1a; 方式一&#xff1a; -&#xff09;在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -&#xff09;导出 # 导出镜…...

android安卓用Rime

之前 [1] 在 iOS 配置用上自改方案 [2]&#xff0c;现想在安卓也用上。Rime 主页推荐了两个安卓平台支持 rime 的输入法 [3]&#xff1a; 同文 Tongwen Rime Input Method Editor&#xff0c;但在我的 Realme X2 Pro 上似乎有 bug&#xff1a;弹出的虚拟键盘只有几个 switcher…...

每日一博 - 三高系统架构设计:高性能、高并发、高可用性解析

文章目录 引言一、高性能篇1.1 高性能的核心意义 1.2 影响系统性能的因素1.3 高性能优化方法论1.3.1 读优化&#xff1a;缓存与数据库的结合1.3.2 写优化&#xff1a;异步化处理 1.4 高性能优化实践1.4.1 本地缓存 vs 分布式缓存1.4.2 数据库优化 二、高并发篇2.1 高并发的核心…...

C++ 中的引用(Reference)

在 C 中&#xff0c;引用&#xff08;Reference&#xff09;是一种特殊的变量类型&#xff0c;它提供了一个已存在变量的别名。引用在很多场景下都非常有用&#xff0c;比如函数参数传递、返回值等。下面将详细介绍 C 引用的相关知识。 1. 引用的基本概念和语法 引用是已存在…...

负荷预测算法模型

1. 时间序列分析方法 时间序列分析方法是最早被用来进行电力负荷预测的方法之一&#xff0c;它基于历史数据来构建数学模型&#xff0c;以描述时间与负荷值之间的关系。这种方法通常只考虑时间变量&#xff0c;不需要大量的输入数据&#xff0c;因此计算速度快。然而&#xff…...

【C语言】内存管理

【C语言】内存管理 文章目录 【C语言】内存管理1.概念2.库函数3.动态分配内存malloccalloc 4.重新调整内存的大小和释放内存reallocfree 1.概念 C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。 在 C 语言中&#xff0c;内存是通过…...

deepseek大模型本机部署

2024年1月20日晚&#xff0c;中国DeepSeek发布了最新推理模型DeepSeek-R1&#xff0c;引发广泛关注。这款模型不仅在性能上与OpenAI的GPT-4相媲美&#xff0c;更以开源和创新训练方法&#xff0c;为AI发展带来了新的可能性。 本文讲解如何在本地部署deepseek r1模型。deepseek官…...

动态规划DP 最长上升子序列模型 拦截导弹(题目分析+C++完整代码)

概览检索 动态规划DP 最长上升子序列模型 拦截导弹 原题链接 AcWiing 1010. 拦截导弹 题目描述 某国为了防御敌国的导弹袭击&#xff0c;发展出一种导弹拦截系统。 但是这种导弹拦截系统有一个缺陷&#xff1a;虽然它的第一发炮弹能够到达任意的高度&#xff0c;但是以后每…...

缩位求和——蓝桥杯

1.题目描述 在电子计算机普及以前&#xff0c;人们经常用一个粗略的方法来验算四则运算是否正确。 比如&#xff1a;248153720248153720 把乘数和被乘数分别逐位求和&#xff0c;如果是多位数再逐位求和&#xff0c;直到是 1 位数&#xff0c;得 24814>145 156 56 而…...

Baklib赋能企业实现高效数字化内容管理提升竞争力

内容概要 在数字经济的浪潮下&#xff0c;企业面临着前所未有的机遇与挑战。随着信息技术的迅猛发展&#xff0c;各行业都在加速推进数字化转型&#xff0c;以保持竞争力。在这个过程中&#xff0c;数字化内容管理成为不可或缺的一环。高效的内容管理不仅能够优化内部流程&…...

【视频+图文讲解】HTML基础2-html骨架与基本语法

图文教程 基本骨架 举个例子&#xff0c;下图所展示的为html的源代码 -!DOCTYPE&#xff1a;表示文档类型&#xff08;后边写的html表示文档类型是html&#xff09;&#xff1b;其中“&#xff01;”表示声明 只要是加这个声明标签的&#xff0c;浏览器就会把下边的源代码当…...

消息队列篇--原理篇--常见消息队列总结(RabbitMQ,Kafka,ActiveMQ,RocketMQ,Pulsar)

1、RabbitMQ 特点&#xff1a; AMQP协议&#xff1a;RabbitMQ是基于AMQP&#xff08;高级消息队列协议&#xff09;构建的&#xff0c;支持多种消息传递模式&#xff0c;如发布/订阅、路由、RPC等。多语言支持&#xff1a;支持多种编程语言的客户端库&#xff0c;包括Java、P…...

【力扣每日一题】存在重复元素 II 解题思路

219. 存在重复元素 II 解题思路 问题描述 给定一个整数数组 nums 和一个整数 k&#xff0c;要求判断数组中是否存在两个 不同的索引 i 和 j&#xff0c;使得&#xff1a; nums[i] nums[j]且满足 abs(i - j) < k 如果满足上述条件&#xff0c;返回 true&#xff0c;否则…...

React第二十八章(css modules)

css modules 什么是 css modules 因为 React 没有Vue的Scoped&#xff0c;但是React又是SPA(单页面应用)&#xff0c;所以需要一种方式来解决css的样式冲突问题&#xff0c;也就是把每个组件的样式做成单独的作用域&#xff0c;实现样式隔离&#xff0c;而css modules就是一种…...

本地运行大模型效果及配置展示

电脑上用ollama安装了qwen2.5:32b&#xff0c;deepseek-r1:32b&#xff0c;deepseek-r1:14b&#xff0c;llama3.1:8b四个模型&#xff0c;都是Q4_K_M量化版。 运行过程中主要是cpu和内存负载比较大&#xff0c;qwen2.5:32b大概需要22g&#xff0c;deepseek-r1&#xff1a;32b类…...

愿景:做机器视觉行业的颠覆者

一个愿景&#xff0c;两场战斗&#xff0c;专注制胜。 一个愿景&#xff1a;做机器视觉行业的颠覆者。 我给自己创业&#xff0c;立一个大的愿景&#xff1a;做机器视觉行业的颠覆者。 两场战斗&#xff1a;无监督-大模型 上半场&#xff0c;无监督。2025-2030&#xff0c;共五…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

聊一聊接口测试的意义有哪些?

目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开&#xff0c;首…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...