上一篇介绍了EDA(Extensive Display Array)拼接屏大规模图像处理(https://blog.csdn.net/gordon3000/article/details/123232508?spm=1001.2014.3001.5501)。这里分析一下拼接屏显示图像的工作过程,先上Master机部分的代码


//  99示例代码  ——打开一个 TIFF 文件,在拼接屏幕墙上显示图像。
//                  在拼接屏多个显示单元上显示图像,用于演示多屏接 EDA(Extensive Display Array,大规模显示阵列)程序编写。
//
//  拼接屏幕墙由一系列的显示屏组成,一些屏幕墙无论拼接多大都只能显示4k(3840*2160)像素,而另一种显示方式则能实现单屏4K显示,
//  假如你的屏幕墙为8*12个4K显示单元,你就可以一次同时显示10亿像素。这里介绍的EDA方式可以帮助你实现后一种显示方式。
//
//  EDA设计为一台主机(Master)带若干从机(Slave)的工作模式。每台Slave机可以挂接1或者多个显示单元。Slave机以行列二维方式排列,
//  m*n个Slave机拖动 M*N个显示单元工作。
//
//  要运行此演示程序,你需要有一台主机,安装好最新版支持EDA的RSD软件。然后你需要2台普通台式机做Slave机,假设你的Slave机显示器
//  就是屏幕墙上的显示单元。在Slave机上安装一下随RSD一起发布的服务程序(GGServer)。如果你手头没有2台Slave机,使用1台也可以,
//  只是无法观察拼接结果。或者只有一台主机,也可以将主机既当Master又当Slave,也能演示此示例程序。本示例程序有2个扩展名为.c的
//  代码文件,这个是“0-0-EDA拼接屏示例-Master端.c”,另一个为“0-0-EDA拼接屏示例-Slave端.c”。不要执行这个Slave端的文件,但是
//  要将这个文件放在下面代码指定可以存取的目录中。
//
//  从两台节点机的演示可以类推出多台计算机带动的屏幕墙的显示结果。EDA设计支持256台节点机的工作模式。进一步的详细介绍参阅随
//  本示例代码一起发布的说明。
//
//  EDA除了屏幕墙展示功能外,其最初设计的功能是大规模集群计算。参阅99脚本语言开发说明,了解如何编程实现大规模集群计算。
//
//  李国春 2022年 2月 20日
/
int displays[2][2];         //节点机接入的显示器数目。
int dispsize[2][2][8][2];   //显示器分辨率,前两维指定一个节点机,第3维是该节点机接入的显示器个数(最多不超过8个)//第4维是显示分辨率,[0] = 显示器像素行数, [1] = 显示器像素列数
int row,col;                //节点机的行数和列数
int nHeight,nWidth;         //节点机上单个显示单元的图像高度和宽度
int colorchs;
double scl[] = {1.0,1.0,1.0};
double ofs[] = {0.0,0.0,0.0};main(){int width,height,bands,datatype;STRING name = OpenFileDialog(TRUE,"*.tif;*.tiff");          //OpenFileDialog 取来一个tif文件名Print("选择文件:%s",name);int b = TifInqDataInfo(name,height,width,bands,datatype);    //读取这个 TIFF 的高度、宽度、波段数和数据类型if(UINT16BL != datatype)                                    //只处理双字节无符号整型数据,不是就返回{Print("只支持双字节无符号整型数据");return -1;}//为 TIFF 文件申请内存,以BSQ格式存放。(注意,数据规模过大这样全部使用内存,申请可能会失败)WORD buffer[bands][height][width];// TIFF 文件数据读入上面申请的内存中(执行完此行后,可以右击 buffer,从菜单“SeeSee”可以观察数据知否正确)int e = TifReadData(name,buffer);if(e <= 0){return -1;Print("读取 TIFF 格式数据错误");}Print("读取 TIFF 格式数据成功");colorchs = 3;//数据波段不足3个时,使用颜色通道也响应减少if(bands < 3){colorchs = bands;}//将TIFF各通道数据变换成用于显示的 RGB 数据///int chs[] = {1,2,3};//合成图像使用的数据通道,BYTE rgb[colorchs][height][width];//存放全部 RGB 图像数据RasterToRGB(buffer,height,width,bands,datatype,rgb,chs,0.001,0.999,BSQ);Print("双字节无符号整型数据转换为单字节数据");//启动节点机进程/Print("准备在节点机启动进程程序");ProcessBegin(displays,dispsize);//获取节点机的个数(返回值),nd的第1个元素(nd[0])是节点机行数,nd的第2个元素(nd[1])是节点机的列数int nd[2];int n = ProcessNodes(nd);Print("共 %d 台节点机,%d 行 %d 列",n,nd[0],nd[1]);//遍历所有节点机,查询节点机显示单元数目和显示分辨率for(int i=0; i<nd[0]; i++){for(int j=0; j<nd[1]; j++){ProcessTo(i,j);//这里指定特定的节点机(不指定时使用99预先排列顺序)。GetSlaveDispInfo(i,j);//这个进程函数获取各节点机的显示单元信息(函数在下面)}}Sleep(2000);//节点机上有数据回收,应该查询所有节点机线程应答,这里简单等一秒代替Alloc(BYTE bmpbits[colorchs][dispsize[0][0][0][0]][dispsize[0][0][0][1]];);Print("第一台节点机主显示器显示分辨率,%d 行 %d 列",dispsize[0][0][0][0],dispsize[0][0][0][1]);//申请全局变量,用于存放向每个节点机发送的图像数据(单字节 RGB),假设所有节点机显示设备分辨率一致nHeight = dispsize[0][0][0][0];nWidth = dispsize[0][0][0][1];int pos[3] = {0,0,0};    //各节点机上的显示起始位置int size[3];          //各节点机上显示块的范围(1屏)size[0] = colorchs;     //图像通道的波段数size[1] = nHeight;       //节点机单屏显示窗口的高度size[2] = nWidth;        //节点机单屏显示窗口的高度int ypos = 500;          //节点机显示图像左上角在主图像中的起始位置 y 坐标int xpos = 500;         //x 坐标int bmpID;//遍历所有节点机,在上面逐个启动1个过程进程程序/for(i=0; i<nd[0]; i++){for(j=0; j<nd[1]; j++){pos[1] = ypos + i*nHeight;pos[2] = xpos + i*nWidth;bmpbits = Slab(rgb,pos,size);FlushSlave(i,j,bmpbits,colorchs,scl,ofs);ProcessCall("D:\\99Codes\\99-EDA拼接屏示例-Slave端.c");Print(" 第 %d 行 %d 列节点机进程已经启动",i+1,j+1);}}//通过向节点机发送消息,循环更新各个节点机上的显示内容//Print("节点机开始显示图像");for(int o=0; o<20; o++)//指定循环次数(或者定时){//更新显示位置ypos = ypos+200;xpos = xpos+200;for(i=0; i<nd[0]; i++){for(j=0; j<nd[1]; j++){pos[1] = ypos + i*nHeight;//计算第  i行第 j 列的节点机应该显示部分的位置if(pos[1] + nHeight >= height){pos[1] = height - nHeight;}pos[2] = xpos + j*nWidth;if(pos[2] + nWidth >= width){pos[2] = width - nWidth;}bmpbits = Slab(rgb,pos,size);//为该节点机准备图像FlushSlave(i,j,bmpbits);//更新节点机显示数据SendMessageSlave(i,j,1,"GGM_NEWBITMAP");//发送显示更新消息Sleep(5);}}}ProcessEnd();//结束所有节点机上的进程Print("节点机进程程序结束");Print("主程序结束");return;
}//
//这个进程函数被分配到所有节点机上执行,获取各节点机的显示单元信息
process GetSlaveDispInfo(int i,int j)
{// Monitors()函数获取本机显示器数目,并将该数据上传至主机标记的第i行j列的 displays[i][j]displays[i][j] = Monitors();for(int k=0; k<displays[i][j]; k++){//GetScreenSize()函数获取本机第 k+1(不是 0 Based)显示单元的尺寸并上传,注意dispsize[i][j][k]是2个数据(第4维)dispsize[i][j][k] = GetScreenSize(k+1);Flush(dispsize[i][j][k]);//刷新主机数据}Flush(displays[i][j]);//刷新主机数据
}

第36~43行通过交互方式选择一个TIFF文件,其中第38行的TifInqDataInfo()函数获得该TIFF文件的几个基本信息,如果数据类型不是双字节无符号整型就不继续处理。

第45~55行读取该TIFF文件的数据,存入一个3维数组buffer[bands] [height][width],数组各维这样排列表示数据是BSQ格式(99数组和子变量有介绍,子变量不是打错了,就是子,不是自)。

第57~67行将双字节无符号整型变换成单字节,统一变换供各个节点机(Slave)使用,节省传输和处理时间。

第71行的 ProcessBegin(displays,dispsize);函数连接各Slave机,准备启动进程。在后面的第150行结束。注意ProcessBegin函数有两个参数,这是Master的公有变量,发送给各节点机的,个数不限,也可以没有。第1个变量int displays[2][2]表示该节点机的显示单元数(显示器个数)。例如displays[0][0]表示第1行第1列的节点机接入的显示单元数。第2个变量int dispsize[2][2][8][2]表示显示单元的分辨率。例如dispsize[0][0][1][0]的值如果是1080,表示第1行第1列的节点机的第2个显示单元的垂直分辨率为1080像元。任意共有变量都可以通过ProcessBegin函数传递以保证任何节点机能够对其存取或者复制。共有变量也可以不在这里申明,然后强制暴露给各节点机。

第74行到第89行收集各节点机显示信息。由84行GetSlaveDispInfo(i,j)函数负责收集。这个函数在下面声明和定义(159~170行)process GetSlaveDispInfo(int i,int j)。函数由process关键字定义,表示该函数是一个在节点机上执行的线程函数,而不是在Master主机上运行。不是本机(Master)的线程称为过程线程。就是说,在每台节点机上都要启动这个函数的一个线程,收集到节点机信息,填入公有变量,方便Master存取。process关键字定义过程线程,下面还有在节点机上定义的过程进程(第116行ProcessCall函数)。可能同学们会问,有了 process关键字了,为什么还要ProcessCall函数。其实ProcessCall是为了更复杂的应用,过程线程一个函数不够用了需要更多的过程线程,像Slave端事件的的收集等。打个比方,触摸屏大屏的某一个显示单元上,人为输入了一个滚屏或者缩放的手势。在Slave端出发了一个事件,但是该事件并不在本Slave端处理,因为那么多Slave机你自己处理了别人怎么知道?而是通过一个哑函数关联到Master的事件响应函数。Master分析该事件,然后通知所有的Slave响应该事件,才能使整个大屏看起来是一个整体。注意:触摸屏手势RSD只是预留的,没做呢。因为其它所有我提及的技术都是我们已经完成的,这个没有,不要引起误会。

第96和第97行定义一个三维数据块的起点和大小,从原始数据裁剪1块数据发送的各个节点机。

第107~119行,遍历各节点机,其主要目的是在每个Slave上都启动过程进程,第116行rocessCall("D:\\99Codes\\99-EDA拼接屏示例-Slave端.c");。ProcessCall函数的参数是一个文件名,也就是需要在Slave上运行的代码。

从123~148行循环向Slave机刷新20次,就是动态演示一下。第128和第130行遍历各Slave。第142行根据当前Slave位置切来一块对应的图像数据,第143行将这块数据强制刷新到Slave机(其实这里也可以将所有数据预先传送到Slave,或者直接存放在共享存储区,比这种临时切了再传效率高)。

第144行向Slave发送了一个字符串消息"GGM_NEWBITMAP"。这个消息在Slave机代码里有一个消息映射,关联到一个过程线程函数。发消息等于通知Slave执行这个线程函数。也就是Slave显示动作的主体。

第150行Master结束过程进程。151行的提示错了,不是节点机进程结束,Master结束后Slave还可能继续执行。

下面是为Slave端准备的代码(Master端动态发送到Slave端的代码)。

/
//  GeoGeo示例代码  ——  刷新Slave显示
//
//  李国春 2014年 8月 23日,2022 22 23修改extern bmpbits;
extern colorchs;
extern scl;
extern ofs;
int winID,bmpID,width,height;
main()
{bmpID = 0;int sz[2] = GetScreenSize(1);//这里仅使用第一个显示器height = sz[0];width = sz[1];//消息映射,接收来自主机的 GGM_NEWBITMAP 消息,并由函数 OnGGNewBitmap 响应MapMessage("GGM_NEWBITMAP","OnGGNewBitmap");//由主机传来的数据创建一个位图(仅窗口大小)bmpID = CreateBitmap(height,width,bmpbits,colorchs,scl,ofs);//创建窗口SuspendEvent("OnDraw");//创建完成前禁止显示刷新winID = CreateWindow("RSD 99窗口",VIEW);//创建普通视图窗口ModifyStyle(winID,0,20);MoveWindow(winID,0,0,width,height);SetStaticTextColor(winID,255,0,0);SetCtrlFont(winID,48,"",0,0,1000);ResumeEvent("OnDraw");//允许显示刷新UpdateView(winID);//强制刷新
}/
//缺省的窗口重绘消息响应函数
event OnDraw(int id)
{if(id == winID){DrawBitmap(winID,bmpID);STRING str = "RSD-EDA(Extensive Display Array,大规模显示阵列)演示程序";CreateStaticCtrl(winID,str,200,200,1520,50);}
}/
//响应主机消息,更新数据
event OnGGNewBitmap(int id,STRING msg)
{//删除上次旧的位图,再用更新后数据创建一个新位图(很低效,示意代码)SuspendEvent("OnDraw");if( 0 != bmpID ){DeleteBitmap(bmpID);}bmpID = CreateBitmap(height,width,bmpbits,colorchs,scl,ofs);ResumeEvent("OnDraw");UpdateView(winID);//强制刷新
}

第6到第9行是Master的公有变量,可以在Slave端使用,记得必要时Flush刷新同步。

第19行 MapMessage("GGM_NEWBITMAP","OnGGNewBitmap"); 就是上面提到的消息映射,对应的函数是第52~64行,这是一个消息响应函数,不是事件。不过逻辑一样都使用了一个 event 关键字做函数开头。然后在第60行将传送来的数据块做成一个位图。再然后在63行强制刷新显示,这个机制和VC++是一样的。

注意,在55和61行将OnDraw()挂起和恢复,防止数据更新期间有强制显示刷新发生。

第22~35行进行一些初始化工作,也进行了第1次的显示。

第40~48行是重绘的消息响应函数。主要负责窗口的图形图像以及文字的绘制,与VC++窗口中的同名函数差不多。不同的是该函数带的一个参数不是设备上下文指针,而是一个窗口ID,当有多个窗口时在该参数指向的窗口内绘图。OnDraw()等少数常用消息响应函数不需要消息映射。

是不是有点小复杂? 熟悉了其中逻辑关系或者使用过VC++的会好一些。欢迎多提宝贵意见。

RSD 99脚本语言拼接屏幕墙实像元显示程序剖析相关推荐

  1. 怎样用C语言数码管编写E1显示程序,跪求单片机0~99数码管显示用C语言编写的程序...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 #include #define uint unsigned int #define uchar unsigned char uint temp; sbi ...

  2. c语言如何扩大字体,C语言图形汉字及放大显示程序

    #include 〈graphics.h〉 #include 〈stdio.h〉 #include 〈fcntl.h〉 #include 〈io.h〉 #include 〈stdlib.h〉 #inc ...

  3. lua游戏代码_在游戏中如何使用LUA脚本语言

    当你希望在你的游戏开始的时候读取一些信息,以配置你的游戏,这些信息通常都是放到一个文本文件中,在你的游戏启动的时候,你需要打开这个文件,然后解析字符串,找到所需要的信息. 或许你认为这样就足够了,为什 ...

  4. 【编译和解释】【源代码和目标代码】【静态语言和脚本语言】

    [编译和解释] 基本概念 1.源代码: 2.目标代码: 3.如何把人类可以阅读的程序变成机器可以执行的目标代码 ①编译: ②解释: ③对比编译和解释: ④静态语言和脚本语言: 基本概念 1.源代码: ...

  5. JDK6的新特性之十二:脚本语言支持(Scripting)

    概述 JDK6增加了对脚本语言的支持( JSR 223),原理上是将脚本语言编译成bytecode,这样脚本语言也能享用Java平台的诸多优势,包括可移植性,安全等,另外,由于现在是编译成byteco ...

  6. 在游戏中如何使用LUA脚本语言

    当你希望在你的游戏开始的时候读取一些信息,以配置你的游戏,这些信息通常都是放到一个文本文件中,在你的游戏启动的时候,你需要打开这个文件,然后解析字符串,找到所需要的信息. 是的,或许你认为这样就足够了 ...

  7. 使用99编程 —— EDA拼接屏大规模图像处理

    99(GeoGeo)是什么?99是一个用于编写计算机程序的脚本语言.虽然面世也有10多年了,但是估计大多数人听都没有听说过99.这也难怪,在众多的优秀编程语言中99这种解释脚本语言要想出头露面也并非易 ...

  8. JavaScript基础知识与脚本语言总结

    1 Aptana插件安装 1.Aptana插件安装 <1>Aptana是一个非常强大,开源,JavaScript-focused的AJAX开发IDE. <2>它的特点包括: J ...

  9. 随笔二——JavaScript脚本语言

    模块三 JavaScript脚本语言 JavaScript(简称"JS")由Netscape公司的Brendan Eich在网景导航者浏览器上首次设计实现而成.为了确保不同的浏览器 ...

最新文章

  1. 读书笔记2013第13本:《怎样解题》
  2. java实现局域网内单对单和多对多通信的设计思路
  3. React系列---Redux高阶运用
  4. 鼠标样式(cursor)
  5. centos6.5下安装docker
  6. Web 安全开发规范手册 V1.0
  7. 计算机基础和操作系统基础知识测试,计算机基础知识和操作系统.doc
  8. 夺命雷公狗---Smarty NO:19 html_options函数
  9. RB750 固件升级 图文
  10. maya导入abc动画_UE4 Alembic 动画资产流程的整理
  11. 从这三个维度说一说,如何做一名具有产品思维的UI设计师?
  12. 我只会HelloWorld,但是我却完成了一个SpringBoot项目!(1)
  13. linux 查询dhcp服务,查找局域网中的DHCP服务器
  14. 什么是switch语句?
  15. 随机变量的函数的分布
  16. 关于error: The following untracked working tree files would be overwritten by checkout的解决方案
  17. Linux下chrony授时监测脚本
  18. php 当地天气预报,php 天气预报代码 采集自中央气象台范围覆盖全国_PHP教程
  19. 关于飞机场的话题(1) ——关于虹桥机场和浦东机场
  20. 计算机网络基础-五层因特网协议栈

热门文章

  1. Flowable(二):数据库详情
  2. 用java的二维数组做一个简易计算优惠小程序(附带源码)
  3. 权威机构统计:2021 年最佳数据中心网络公司,中国华为和H3C上榜
  4. 简洁风个人主页(3) js背景图片随机切换
  5. 半导体器件物理【20】PN结 —— 费米能级与电流关系、接触电势差
  6. 浏览器获得电脑麦克风音频进行广播发声(非录音播放)
  7. Python3制作鼠标拾色器并显示十六进制数值(有单独窗口显示取色)
  8. 网络推广-方法和技巧
  9. html输入名字自动跳出的信息,1、在excel输入名字自动跳出相应的部门或是工号...
  10. CSharp生成二维条码的步骤