easyx 绘图库建议:开放绘图函数,所有构成绘制图形图像的像素点坐标及运动轨迹路线数据,供创造者二次使用。

3

0.标题

easyx 绘图库建议:f规定好坐标点的绘图函数绘制过程像素点运动路径坐标,供创造者使用。

1.发现

就是本身有一些绘制函数,构造他们 ,就需要大量的数学知识,并且大量产生与绘制的运动轨迹坐标点 ,我们使用绘图库的时候,老是需要重复造轮子来驱使运动这些绘图函数,但本身绘图函数,他就是一个造好的轮子,为什么不用轮子来驱使轮子呢?我称之为绘图函数绘制运动轨迹坐标的递归。

2.例如

我画一条规定好坐标直线,让一个小球前后运动反复在这条直线上来回运动,直线很简单,就是一个变量数字加和减,
但是我让这个小球运动在一条规定坐标了直斜线上呢?问题可不就是加和减这么简单,多了一个要素就是斜率。
意思一般情况下就是让一个小球斜着运动,就得重新造一个类似于画直斜线的算法,让他反复去运动,
但是绘图函数直线本身就可以画斜线呀  ,如果可以直接得到绘图函数画的这条斜线上所有构成直斜线像素点的坐标,再通过循环把它循环坐标出来,不就是重新利用,不需要再写算法的吗?不需要再次计算了吗,直斜线只是一条例子,比如矩形,圆形,还有扇形,多段线曲线,等其他绘图函数,呢?开放构成他们图形的绘制路径像素坐标点,就可以让其他绘图函数运动在这个点上,不需要在设计类似于此函数的运动算法。用魔法来打败魔法

3.建议

我的提案是绘图函数,可以组成一个全新的一个绘图数据类型,即构成该绘图函数本身可以通过规定坐标,所有像素点坐标运动轨迹数据类型,该函数可以不绘制出图形,但是可以绘制它的运动轨迹数据,供其他绘图函数使用,估计最好用的例子就是多段线,曲线。

4.程序源码实例

以下是实现一个简单用坐标定位并计算获取所有构成直斜线图像像素点坐函数标例子

使用函数画直斜线,并把画直斜线的运动轨迹坐标保存至数组,再次循环使用,用小球来回前后重复运动可视化直斜线绘制过程。

#include <graphics.h>
#include<math.h>
// 缓冲区画点。
void putpixelqBuffer(DWORD* pibu, int x, int y, int w, int h, COLORREF color)    
{   long zb = (h * y), zb1 = (w - x), ab2 = zb - zb1;
    pibu[abs(-ab2)] = BGR(WHITE);
}
// 交换值。
void abd(int* x1, int* x2, int* y1, int* y2, int* i)
{   *i = *x1; *x1 = *x2; *x2 = *i;
    *i = *y1; *y1 = *y2; *y2 = *i;
}
// 计算求斜率。
long k(int i, float x, float y){return (long)round((x / y) * i);}  
// 点缓冲画任意直线。
int line_s1(int x1, int y1, int x2, int y2, int line_xy[][2])   
{
    int w1 = getwidth(), h1 = getheight(), i, w, h, len = 0;
    if (x1 >= w1 || x2 >= w1 || x1 <=0 || x2<=0|| y1 >= h1 || y2 >= h1 || y1 <= 0 || y2 <= 0)return 0;
   
    DWORD* p = GetImageBuffer();
    if ((w = abs(x1 - x2)) >= (h = abs(y1 - y2)))
    {  if (x1 > x2)abd(&x1, &x2, &y1, &y2, &i);
        if (y1 < y2)
            for (i = 0; i < w; ++i)
                putpixelqBuffer(p, line_xy[i][0] = x1 + i, line_xy[i][1] = y1 + k(i, h, w), w1, h1, WHITE);
        else
            for (i = 0; i < w; ++i)
                putpixelqBuffer(p, line_xy[i][0] = x1 + i, line_xy[i][1] = y1 - k(i, h, w), w1, h1, WHITE);
    }
    else
    {    if (y1 > y2)abd(&x1, &x2, &y1, &y2, &i);
        if (x1 < x2)
            for (i = 0; i < h; ++i)
                putpixelqBuffer(p, line_xy[i][0] = x1 + k(i, w, h), line_xy[i][1] = y1 + i, w1, h1, WHITE);
        else
            for (i = 0; i < h; ++i)
                putpixelqBuffer(p, line_xy[i][0] = x1 - k(i, w, h), line_xy[i][1] = y1 + i, w1, h1, WHITE);
    }
    putpixelqBuffer(p, x2, y2, w1, h1, WHITE);
    return i;
}

int main()
{   initgraph(900, 900);
    // 定义消息变量
    ExMessage m;		
    bool tr = true;
   
    int i = 0, len = 0, xx1 = 1, yy1 = 1, xx2 = 600, yy2 = 600;
    // 存放一条直线的绘制过程的 x_y 坐标二次使用。
    int line_xy[2000][2] = { 0 };
    BeginBatchDraw();
        while(true)
        {   // 改造后的画直线函数,使用二级数组记录画直斜线的所以像素点坐标,并返回像素点坐标长度,搭配 FOR 循环就可以读取。
            len = line_s1(xx1, yy1, xx2, yy2, line_xy);
            if (tr == true)
            {
                if (tr == true)
                    if (i < len)
                    {   // 画跟随规定好坐标直斜线上向下的运动红色小球,
                        i++;
                       setlinecolor(WHITE);
                       fillcircle(line_xy[i][0], line_xy[i][1], 20);
                       setlinecolor(RED);
                       fillcircle(line_xy[i][0], line_xy[i][1], 15);
                       Sleep(5);
                       FlushBatchDraw();
                    }
                if (i == len)
                {tr = false;
                }
            }
            else if (tr == false)
            {
                if (tr == false)
                    if (i > 0)
                    {   // 画跟随规定好坐标直斜线上向上的运动红色小球,
                        i--;
                        setlinecolor(WHITE);
                        fillcircle(line_xy[i][0], line_xy[i][1], 20);
                        setlinecolor(RED);
                        fillcircle(line_xy[i][0], line_xy[i][1], 15);
                        Sleep(5);
                        FlushBatchDraw();
                    }
                if (i == 0)
                {tr = true;
                }
            }
        }
    EndBatchDraw();
    getmessage(EX_CHAR);
    closegraph();
    return 0;
}
ava
随波逐流

2023-3-3

3

首先,这个功能势必会影响性能。在性能和功能面前如何选择,是个学问。

目前来看,大部分人不需要这个功能。如果加了这个功能,对大部分人来说,都会受到性能影响。少部分需要的,可以通过图形学算法自己实现,并不复杂。

除了性能,还有实现方式上的问题。比如,如果要实现,那应该使用回调函数的形式提供每个点的坐标。但是很多时候,通过回调函数的方式并不方便,例如,你想实现一个小球沿着一条斜线运动,画直线函数可以返回直线上的每个点坐标,并通过回调函数让用户得到每个点的坐标,但这个时候,用户不能在回调函数里加 Sleep 延时,也就没有办法在回调函数里实现让一个小球沿着这条斜线运动。

所以其实,程序自己实现比较方便些。codebus 就有一些作品这么做了,自己写了图形学算法获得需要的每个点坐标并进行后续的处理。

ava
慢羊羊

2023-3-4

我的看法吧,主要是对其他运动轨迹算法不熟纯纯的小白,所以才想偷懒,不过代码巴士里的开源代码的定很顶, -  随波逐流  2023-3-5
3

先从第一个角度来看:“但本身绘图函数,他就是一个造好的轮子,为什么不用轮子来驱使轮子呢?”,那么是否有哪个绘图 API 提供了你所说的功能呢?SDL、DirectX、OpenGL 都没有提供这样的功能。

从 EasyX 原理上再来说:EasyX 本质上只是对于 GDI 的一个封装,GDI 没有提供这样的功能自然也就代表 EasyX 不可能实现这个功能除非 EasyX 底层实现修改,而显然,抛弃 GDI 的硬件加速功能来实现这个功能属于捡了芝麻丢西瓜。

从产品设计上来讲:EasyX 设计之初就是为了辅助图形学学习,所以当然,它不应该提供给更多的高层 API(尽管在我看来学习图形学只需要 GetImageBuffer FlushBatch BeginBatchDraw EndBatchDraw 这些函数),它本就不适合去实现这些功能。

从性能上来讲:假若 EasyX 可以提供这个功能,那是否需要提供安全性检查?如果你给的数组过小呢?所以每一次的 line 都代表着一次数组合规性检查吗?

从你的实现上来讲:你所需要的功能完全用不到这么冗杂的实现,完全用一次函数来实现:令函数 f(x)=kx+b 表示圆在 x 点的 y 坐标,所以一个圆在沿着某直线运动的圆心坐标可以表示为 O(x, f(x)),若 x 随时间 t 变化,则就是 O(t, f(t)),所以,完全没必要得到一个线段的每一个点阿,你这么做的意义何在呢....

补充一下我说的方法的实现代码:

#include <graphics.h>
#include <conio.h>

float f(int x)
{
	return 2 * x + 1;
}

int main()
{
	POINT p1 = { 0	, f(0) };
	POINT p2 = { 200, f(200) };

	initgraph(640, 480);

	BeginBatchDraw();

	int delta	= 1;
	int x		= 10;

	settextcolor(BLUE);

	while (true)
	{
		cleardevice();
	
		setlinecolor(RED);
		line(p1.x, p1.y, p2.x, p2.y);
		setlinecolor(GREEN);
		circle(x, f(x), 10);
		putpixel(x, f(x), LIGHTGREEN);

		outtextxy(100, 10, L"圆圈将会绕着如图所示的红线轨迹运动");

		if (x == 195)
		{
			delta = -1;
		}
		if (x == 5)
		{
			delta = 1;
		}

		x += delta;

		Sleep(16);

		FlushBatchDraw();
	}

	EndBatchDraw();

	return 0;
}

再补充,如果觉得手动计算一次函数麻烦的话,我写了一个伪函数版本,可以直接给定两点确定一次函数... 直接 get_function 后就可以直接调用了:

#include <graphics.h>
#include <conio.h>
#include <tuple>
#include <exception>
#include <stdexcept>
#include <stdio.h>

#undef max
#undef min

// 伪函数类
template<class return_type, class input_type>
class fake_function
{
private:
	input_type k;
	input_type b;

public:
	fake_function()
		: k(0), b(0)
	{

	}
	fake_function(input_type k_input, input_type b_input)
		: k(k_input), b(b_input)
	{

	}

public:
	return_type operator()(const input_type& x)
	{
		return k * x + b;
	}
};

// 计算一次函数系数
std::tuple<float, float> cal_function(POINT p1, POINT p2)
{
	float c1 = -p1.y;
	float c2 = -p2.y;

	if (p1.x == p2.x)
	{
		throw std::invalid_argument("方程无解");

		return { 0, 0 };
	}

	return {
		(c2 - c1) / (p1.x - p2.x),
		(p1.x * c2 - p2.x * c1) / (p2.x - p1.x)
	};
}
template<class return_type = float, class input_type = float>
// 通过两点确定一个一次函数
fake_function<return_type, input_type> get_function(POINT p1, POINT p2)
{
	auto function_parameter = cal_function(p1, p2);

	return fake_function<return_type, input_type>(std::get<0>(function_parameter), std::get<1>(function_parameter));
}

int main()
{
	POINT p1 = { 0, 0 };
	POINT p2 = { 640, 480 };

	decltype(get_function(p1, p2)) f;

	// 获得一次函数
	try
	{
		f = get_function(p1, p2);
	}
	catch (const std::string& error_string)
	{
		printf("%s", error_string.c_str());

		exit(-1);
	}

	initgraph(640, 480);

	BeginBatchDraw();

	int delta	= 1;
	int x		= 10;
	int radius	= 20;

	while (true)
	{
		cleardevice();

		auto circle_center = f(x);

		setlinecolor(RED);
		line(p1.x, p1.y, p2.x, p2.y);

		setlinecolor(GREEN);
		circle(x, circle_center, radius * 2);
		putpixel(x, circle_center, LIGHTGREEN);

		settextcolor(LIGHTGREEN);
		outtextxy(x + 10, circle_center, L"O");

		settextcolor(BLUE);
		outtextxy(100, 10, L"圆圈将会绕着如图所示的红线轨迹运动");

		if (x == p2.x - radius)
		{
			delta = -1;
		}
		if (x == p1.x + radius)
		{
			delta = 1;
		}

		x += delta;

		Sleep(16);

		FlushBatchDraw();
	}

	EndBatchDraw();

	return 0;
}

所以下次遇到问题要多思考... 不要急于否定或者是要重建什么...

再顺带一提:圆周可以用三角函数,也可以用旋转矩阵,贝塞尔曲线也可以用多项式拟合,都是初中内容,远不及图形学...

ava
Margoo

2023-3-5

嗯针不错,可以再来一点椭圆的和扇形还有多段线多边形的运动轨迹伪代码么? -  随波逐流  2023-3-5
技术讨论社区