EasyXnote5关于批量绘图

简介: EasyXnote5关于批量绘图

问题引入

 之前的讲解中,我们可以发现创建的窗体在进行动画的显示时会出现闪烁现象,本节课将会一步一步探讨如何解决,可以使以后学习中的动画效果更加流畅。

  • 首先创建窗体,利用之前所学习的内容在窗体上绘制圆形,但这次是利用循环绘制出多个圆形,并且使其在窗体内不断运动,为其赋上随即初始坐标及运动方向,设置不同的运动速度,为将其全部保存在窗体内,可以设置为触碰到窗体即可反弹。

绘制画面

实现思路如下

这次设置窗体颜色为白色。

不要忘记使用cleardevice();重新粉刷窗体。

 在实现多个小球同时运行前,先看一个小球的情况,速度随机,初始运动方向随机,当碰到窗体边缘时反弹。

代码如下

int main()
{
  initgraph(800, 600);
  setorigin(400, 300);
  setaspectratio(1, -1);
  setbkcolor(WHITE);
  cleardevice();
  srand(time(NULL));
  int r = 50;
  int x = rand() % 700 - 400+50;
  int y = rand() % 500 - 300+50;
  int vx = rand() % 3 +9;
  int vy = rand() % 3 +9;
  if (x % 2 == 0)
  {
    vx = -vx;
  }
  if (y % 2 == 0)
  {
    vy = -vy;
  }
  while (1)
  {
    cleardevice();
    setfillcolor(BLACK);
    solidcircle(x, y, r);
    x += vx;
    y += vy;
    Sleep(50);
    //碰到墙壁反弹
    if (x >= 400 - r || x <= -400 + r)
    {
      vx = -vx;
    }
    if (y >= 300 - r || y <= -300 + r)
    {
      vy = -vy;
    }
  }
  closegraph();
  return 0;
}

运行后如图

 然而一个小球在窗体内运动是这样,要使所有小球运动起来,就需要记录每个小球的位置,方向,与绘制静态和绘制单个运动的小球不同,所有的小球都需要不断进行重新绘制,需要重复不断地计算每个小球移动后的坐标,不断记录更新。

因为每个小球有很多变量类型,我们创建出一个结构体记录小球的每个变量特征。

typedef struct
{
  int x;
  int y;
  int r;
  int vx;
  int vy;
  COLORREF color;
}ball;

创建一个结构体数组,保存每一个小圆的信息

定义小球数量方便更改看效果

#define NUM 100

创建结构体数组,保存每个小球的数据。

ball balls[NUM];

设置循环,为数组内的所有小球设置初始值

for (int i = 0; i < NUM; i++)
  {
    int m = -400 + r;
    int n = 400 + r;
    balls[i].x = rand() % (n-m + 1) + m;//圆形的全部都不能超出窗体,为之后的碰撞改变方向做铺垫
    m = -300 + r;
    n = 300 + r;
    balls[i].y = rand() %(n-m + 1) + m;
    m = 5;
    n = 7;
    balls[i].vx = rand() % (n-m + 1) + m;//速度在5~7之间
    if (balls[i].x % 2 == 0)
    {
      balls[i].vx = -balls[i].vx;
    }
    balls[i].vy = rand() % (n-m + 1) + m;//速度在5~7之间
    if (balls[i].y % 2 == 0)
    {
      balls[i].vy = -balls[i].vy;
    }
    balls[i].color = (RGB(rand() % 256, rand() % 256, rand() % 256));
  }

这里小圆的初始坐标的位置值得一提,如果想要获得m到n的范围,假设m为-1,n为5。 n-m为6,rand()%6的范围是0~5。

rand()%(m+n+1)的范围是0~6,再加上m,就得到了我们想要的范围。

所以想要得到m到n的数据范围,可以写作rand()%(n-m+1)+m;

 开始进行绘制,利用循环绘制出所有的小球。在第二个for循环中更新小球的位置,判断每个小球是否撞击窗体,然后再次重新绘制,利用计算机的高速计算,就可以实现多个小球同时运动。

(下图调小手机亮度)

while (1)//不断地重新更新小球
  {
    cleardevice();
    for (int i = 0; i < NUM; i++)
    {
      setfillcolor(balls[i].color);
      fillcircle(balls[i].x, balls[i].y, r);
    }
    Sleep(40);
    for (int i = 0; i < NUM; i++)
    {
      if (balls[i].y >= 300 - r || balls[i].y <= -300 + r)
      {
        balls[i].vy = -balls[i].vy;
      }
      if (balls[i].x >= 400 - r || balls[i].x <= -400 + r)
      {
        balls[i].vx = -balls[i].vx;
      }
      balls[i].x += balls[i].vx;
      balls[i].y += balls[i].vy;
    }
  }

运行代码

批量绘图解释

运行后会发现画面有十分严重的闪烁现象。

原因是什么呢?

我们要了解要使程序在窗体上显示图像,需要进行那些步骤

1,程序将图像放置在显示缓存区中

2,显卡将缓存区的数据绘制到屏幕上

 把程序比作快递员,图像就是我们要运送的快递,显示缓存区就是驿站,把快递放在驿站里,等待着显卡将其送给收件人,就是将图像绘制到屏幕上。

 在这里1000个小球被看作一千个快递,快递员要一次一次将每个快递放置在驿站,收件人要一个一个取走包裹,快递员要送一千次,收件人要取1000次,在生活中,快递员通常是一次性运送多个快递,收件人也会一次取出多个快递,这样不用跑那么多次节省效率。

 那么为什么画面会出现一卡一卡的现象呢,就是快递小哥太累了来不及投送某些快递,也就是程序或显卡因为要处理的太多,无法及时工作。

 我们就利用生活中一次性送多个包裹,一次性接受多个包裹的方法来解决画面闪烁问题。将100个快递打包为一个大快递,直接运送大快递,显卡只需要从快递柜中取出一个大的合并的包裹就可以了。这样就可以大大提高绘图效率。

因为是将多个数据打包后传输,所以也就称为批量绘图。

然而批量绘图== 有利也有弊==

 单次绘图的情况下,只要有一个图像传递过来,就直接将其送至显卡,显示在屏幕上,而批量绘图要等到存至100个画面信息才会将其交给显卡,时效性降低了。但大多数程序对于显示的时效性要求没有那么高,所以这种方法还是可行的。因为批量绘图被广泛的使用在各个场景中。

批量绘图使用

先在一个小的例子中解释

int main()
{
  initgraph(800, 600);
  setorigin(400, 300);
  setaspectratio(1, -1);
  setbkcolor(RED);
  cleardevice();
  setfillcolor(RGB(23, 45, 147));
  solidcircle(-320, 0, 40);
  Sleep(1000);
  solidcircle(-220, 0, 40);
  Sleep(1000);
  solidcircle(-120, 0, 40);
  Sleep(1000);
  solidcircle(-20, 0, 40);
  Sleep(1000);
  solidcircle(80, 0, 40);
  Sleep(1000);
  solidcircle(180, 0, 40);
  Sleep(1000);
  getchar();
  closegraph();
}

运行后每隔1秒才会绘制一个圆。

函数BeginBatchDraw

 函数BeginBatchDraw可以开启批量绘图模式,开启批量绘图后,所有的绘图操作将不再进入显示缓存区,直到调用了EndBatchDraw函数结束批量绘图,才会将从BeginBatchDraw函数以来的所有绘图操作放入显示缓存区。

添加两个函数进入上述代码,在第二次绘制圆形后开始进行批量绘制,在最后一次绘制圆前加入结束绘制的函数。

运行后如图

可以发现,前两个绘制结束后,就不再进行绘制了,等了4秒后,后边的四个圆形立马就画出来了。

再介绍一个函数FlashBatchDraw();

 BeginBatchDraw函数使用后,就只能等遇到EndBatchDraw函数才将图像传至显示缓存区。太霸道了吧,FlashBatchDraw给了他一巴掌,说,遇到我FlashBatchDraw,就要把在我之前的图像全部传过去,BeginBatchDraw一看是个大佬,顿时服软,就将遇到FlashBatchDraw前捕获的图像全部放了,然而过了FlashBatchDraw,他就继续捕获图像,不让其到达显示缓存区。BeginBatchDraw是坏人,抓图像,不让走,FlashBatchDraw是侠客,放开那些图像,但没抓BeginBatchDraw,EndBatchDraw就是警察,抓了Begin,解救图像。

 将BeginBatchDraw移至第一个绘图后,EndBetchDraw移至最后,在第三个绘图后加入FlashBatchDraw,运行观察效果。

可以发现结果如我们所料。


回到上述画面闪烁的问题

当累计绘制1000个小球后,在将这1000个小球打包装进显示缓存区,只需要在循环前加上BeginBatchDraw函数,在一千个小球初始化后加入FlashBatchDraw函数,在绘制循环结束时,关闭批量绘图即可。

运行结果

由于插入图片大小有要求,所以不能录制太长的片段

代码如下,大家可自行参考

#include <stdio.h>
#include <easyx.h>
#include <stdlib.h>
#include <time.h>
typedef struct
{
  int x;
  int y;
  int vx;
  int vy;
  COLORREF color;
}ball;
#define NUM 1000
int main()
{
  initgraph(800, 600);
  setorigin(400, 300);
  setaspectratio(1, -1);
  setbkcolor(WHITE);
  cleardevice();
  srand(time(NULL));
  int r = 10;
  ball balls[NUM];
  for (int i = 0; i < NUM; i++)
  {
    int m = -400 + r;
    int n = 400 + r;
    balls[i].x = rand() % (n-m + 1) + m;//圆形的全部都不能超出窗体,为之后的碰撞改变方向做铺垫
    m = -300 + r;
    n = 300 + r;
    balls[i].y = rand() %(n-m + 1) + m;
    m = 5;
    n = 7;
    balls[i].vx = rand() % (n-m + 1) + m;//速度在5~7之间
    if (balls[i].x % 2 == 0)
    {
      balls[i].vx = -balls[i].vx;
    }
    balls[i].vy = rand() % (n-m + 1) + m;//速度在5~7之间
    if (balls[i].y % 2 == 0)
    {
      balls[i].vy = -balls[i].vy;
    }
    balls[i].color = (RGB(rand() % 256, rand() % 256, rand() % 256));
  }
  BeginBatchDraw();
  while (1)//不断地重新绘制更新小球
  {
    cleardevice();
    for (int i = 0; i < NUM; i++)
    {
      setfillcolor(balls[i].color);
      fillcircle(balls[i].x, balls[i].y, r);
    }
    FlushBatchDraw();
    Sleep(40);
    for (int i = 0; i < NUM; i++)
    {
      if (balls[i].y >= 300 - r || balls[i].y <= -300 + r)
      {
        balls[i].vy = -balls[i].vy;
      }
      if (balls[i].x >= 400 - r || balls[i].x <= -400 + r)
      {
        balls[i].vx = -balls[i].vx;
      }
      balls[i].x += balls[i].vx;
      balls[i].y += balls[i].vy;
    }
  }
  EndBatchDraw();
  getchar();
  closegraph();
  return 0;
}

本章结束,希望能给大家带来帮助,谢谢大家

目录
相关文章
|
2月前
|
JSON JavaScript Linux
绘图框架 plotly 知识点补充(绘制子图,图表保存)
绘图框架 plotly 知识点补充(绘制子图,图表保存)
58 13
|
28天前
|
数据可视化 Python
Matplotlab可视化学习笔记(三):如何根据不同的txt文档绘制多条折线图
这篇文章是关于如何使用Python的matplotlib库根据多个txt文档中的数据绘制多条折线图的教程。
25 0
|
4月前
|
数据可视化 Linux 数据格式
`seaborn`是一个基于`matplotlib`的Python数据可视化库,它提供了更高级别的接口来绘制有吸引力的和信息丰富的统计图形。`seaborn`的设计目标是使默认图形具有吸引力,同时允许用户通过调整绘图参数来定制图形。
`seaborn`是一个基于`matplotlib`的Python数据可视化库,它提供了更高级别的接口来绘制有吸引力的和信息丰富的统计图形。`seaborn`的设计目标是使默认图形具有吸引力,同时允许用户通过调整绘图参数来定制图形。
|
5月前
cavans绘图步骤
cavans绘图步骤
|
6月前
|
编解码 数据可视化
R语言动态可视化:绘制历史全球平均温度的累积动态折线图动画gif视频图
R语言动态可视化:绘制历史全球平均温度的累积动态折线图动画gif视频图
|
数据采集 数据格式 Python
GGPLOT -批量创建饼图
本文分享了 数据预处理到 GGPLOT 批量创建饼图 的基本代码过程以供参考
146 0
|
机器学习/深度学习 Python
自动美化你的Matplotlib ,使用Seaborn控制图表的默认值(二)
自动美化你的Matplotlib ,使用Seaborn控制图表的默认值(二)
251 0
自动美化你的Matplotlib ,使用Seaborn控制图表的默认值(二)
|
数据可视化 Python
自动美化你的Matplotlib ,使用Seaborn控制图表的默认值(一)
自动美化你的Matplotlib ,使用Seaborn控制图表的默认值(一)
90 0
自动美化你的Matplotlib ,使用Seaborn控制图表的默认值(一)
|
自然语言处理 数据可视化 数据格式
Q&A | 如何用R批量绘制并导出生存曲线图
Q&A | 如何用R批量绘制并导出生存曲线图
348 0
Q&A | 如何用R批量绘制并导出生存曲线图
|
数据可视化 BI Python
快速绘制动态排序图 — Pyecharts 高级组件 Timeline 实现!
之前写过一篇关于Python 制作 动态排序图的教程,里面利用的是 Matplotlib 中的 animation 函数,文章内容可参考动态排序图的详细制作教程,动态排序图的最终部分效果如下:
快速绘制动态排序图 — Pyecharts 高级组件 Timeline 实现!