VC++项目二
摘要
软件项目内容概述
屏幕广播开发
该软件是应用在 windows 系列操作环境下的,它由两个程序组成,教师机程序和学生机程序。
教师机程序获取教师机屏幕位图,将位图数据压缩后,通过网络在特定的组播地址和端口上将屏幕
位图数据发送出去。
学生机程序从网络上接收教师机屏幕位图数据,解压后显示在学生机的整个屏幕上,学生机
屏幕同教师机屏幕图象保持同步,实现软件分屏显示,替代传统的硬件分屏方式。
该软件非常适用远程计算机教学系统,譬路,在北京的教师演示操作某个计算机软件的使用
时,所有外地的学生机屏幕上都显示教师机屏幕上的内容,即将远端教师的计算机屏幕呈现在每个
学生面前。教师与学生的计算机是通过卫星,光缆,Cable 等能够实现远距离传输的网络连接在一
块的。
从网络的分层协议上来看,本软件属于采用 TCP/IP 协议的应用层软件,与底层的网络实现方
式无关,只要网络支持 TCP/IP 协议即可。所以本软件也可用于本地局域网,事实上,本软件的开
发调试就是在局域网环境下进行的,然后在应用到远程网络上的。
实现原理:
该软件由两个程序组成,教师机程序和学生机程序。
教师机程序首先获得教师屏幕窗口句柄,屏幕实质上也是一个特殊的窗口,这样便可获得屏幕
窗口所对应的位图图象,将这个位图图象的数据通过网络在设定的组播地址和端口上发送出去。
学生机程序在设定的组播地址和端口上接收网络数据,一但接收到老师机屏幕位图图象的完整
数据后,便可根据这些数据在学生机内存中生成教师机屏幕图象。然后,学生机产一个与屏幕同样
大小的无边框窗口,显示在桌面的最顶层,并将在内存中生成教师机屏幕图象粘贴到这个新生成的
窗口上,这样,学生机的整个桌面上将显示为教师机的屏幕图象。
教师机程序在上一幅屏幕图象成功传输后,再次获取当前时刻的屏幕图象。当前屏幕图象根据
计算机的运行情况,与上一幅图象相比,只是局部发生改变,或是完全相同。为了降低网络数据传
输量,节省网络带宽,在传输当前屏幕图象时,教师机程序将比较当前屏幕图象与上一幅图象数据
的内容,获得变化过的图象区域,然后通过网络将当前屏幕图象上变化过的图象区域位置及图象数
据传送给学生机。
学生机程序从网络上接收到教师机当前屏幕图象上变化过的图象区域位置及图象数据后,根据
区域位置用该区域的图象数据更新上幅图象的对应区域数据,使学生的屏幕与老师的屏幕保持一
致,从而实现软件分屏显示的功能。
由于屏幕图象的变化区域形状并不规则,甚至是若干小的不相邻区域,很难找到合适的数据模
型来描述。为了简化程序开发难度,将整个屏幕图象用 32*32 象素区域块进行分割
。这个当前屏幕图象与上一幅图象进行比较时,实际上是逐个按 32*32 象素区域进行比较,只要象
素区域中有一点不同,就说明`这个 32*32 象素区域有变化,另外,为了更有效的降低网络数据的传
送量,节省网络带宽,将每一块 32*32 象素数据进行压缩,压缩后在网络上发送出去。学生机程序
接受到每一个网络数据,并对其进行解压缩,恢复该区域的数据,
技术要点:
VC++项目二
屏幕图象的处理过程
一.位图图象格式
由于我们所获得的屏幕图象是位图格式,所以首先必须了解位图格式。
BMP 文件的结构
文件头
位图信息头
色彩表
位图阵列
1. 文件头
(位图文件结构)
文件头含有位图文件的类型,大小,数据结构等信息,文件头由下面一个结构组成:
typedef struct tagBITMAPFILEHEADER{//BMFH
WORD bfTupe;
DWORD bfSize;
WORD
bfReserved1;
WORD bfReserved2;
bfOffBits;
DWORD
}BITMAPFILEHEADER;
在上面结构中,每一个成员的含义解释是如下:
bfType 表明位图文件的类型,必须为 BM;
bfSize 表明位图文件的大小,以字节为单位;
bfReserved1 代表保留字,必须为 0;
bfReserved2 也是保留字,必须为 0;
bfOffBits 表明位图阵列的起始位置,也就是位图阵列相对于位图文件头的偏移量,以字节
为单位;
由于本软件中没有对位图文件的操作,在获取屏幕位图,显示位图都是在内存中直接进行
的,只需要位图信息头,色彩表,位图阵列数据等就可以了。因此,程序中没有用到位图文件
头这个结构。
2. 位图信息头
位图信息头含有 DIB(device-independent bitmap)与设备无关位图的大小和颜色格式。信息头
由下面一个结构组成:
struct tagBITMAPINFOHEADER{
tyopedef
DWORD biSize;
LONG biHeight;
WORD biplanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
VC++项目二
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
}BITMAPINFOGEADER;
以上每个字段的具体含义解释如下;
biSize 代表 BITMAPINFOGEADER 结构所需要的字节数。
biWidth,biHeigh 代表一象素为单位的 BMP 图象的宽度和高度。
BiPlatles 给出输出设备的平面数,必须置为 1。
biBitCount 给出每个象数的位数。
biCompress 表示该图象所用的压缩类型。
biSizeImage 代表图象字节数
biXPelsPerMeter 代表图象的水平分辨率(以目标设备的每米象素数为单位)。
biYPelsPerMeter 代表图象的垂直分辨率(以目标设备的每米象素数为单位)。
BiClrUsed 给出调色板中图象实际使用的颜色数。若此值为 0,那么图象使用的颜色由
biBitCount 的值确定;若此值不为 0,当 bitBitCount 的值小于 16,则 biClrUsed 给出图形设备驱动
程序或设备驱动程序将访问的实际使用的颜色数。当 bitBitCount 大于等于 16,则 biCirUsed 给出用
于优化调色板性能的参考色彩表的大小。
BiClrImportant 给出对于图象显示来说是重要的颜色索引值。此值若为 0,则所有颜色都是重
要的。
3. 色彩表
色彩表定义了一个颜色表,用于说明图象中的颜色。它有若干个表项,每一个表项都由
RGBQUAD 结构定义了一种颜色,每一种颜色都可由红、绿、蓝三个基色的不同分量组合而成,
RGBQUAD 实际上描述了一种颜色的红、绿、蓝三色的分量。
RGBQUAD 结构定义如下:
typedef struct tagRGBQUAD{//rgbq
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
}RGBQUAD;
色彩表中表项的个数由 biBitCount 来定:
当 biBitCount=1、4、8 时,色彩表分别有 2,16,256 个表项;若某点的象素值为 n,则该象素
的颜色为色彩表中的第 n 项所定义的颜色。
当 biBitCount=24 时,色彩表表项为空;位图阵列的每 3 个字节表示一个象素,这 3 个字节直接
定义了象素颜色中的蓝、绿、红的相对亮度。
在 RGBQUAD 定义的颜色中,兰色的亮度由 rgbBlue 定义,绿色亮度由 rgbGreen 定义,红色的亮
度由 rgbRed 定义,rgbReserved 必须为零。若某表项的值为:FF,00,00,那么他定义的颜色为纯
蓝色。
当 biBitCount=16,32 时,情况较为复杂,并不常用。
4. 位图阵列
位图阵列存放图象的所有象素值。当 biBitCount=1、4、8 时,位图阵列中每个象素的值是该象
素颜色在色彩表中的索引序号。象素值与色彩表是紧密相关的,对于同样的一个位图图象,如果色
彩表不一样,那位图阵列里的象素值也将不一样。
VC++项目二
下图是包含有 4 种颜色,16*4 象素的色彩表与位图数据阵列的信息:
色彩表
颜色索引号
0
1
2
3
红色分量
255
128
0
255
绿色分量
255
255
255
128
蓝色分量
0
255
128
128
位图数据阵列
3
2
0
2
2
2
2
1
1
2
0
2
其中,位图阵列中的每个象素占用 2 个 bits,其数据是象素颜色对应在色彩表中的索引号。
1
0
1
3
1
0
1
3
1
0
0
3
2
0
0
3
3
0
0
1
3
0
0
1
3
2
2
0
3
0
0
3
3
0
0
1
3
0
0
1
1
2
1
2
2
2
1
2
2
0
1
3
图象列阵的产生方法是:从图象的左下脚开始逐行扫描图象,从左到右,从下到上,把图象的
象素值全部记录下来。位图列阵数据的存储格式有压缩和非压缩两种。由于我们用到的是非压缩格
式,所以在此不介绍有压缩格式。
在非压缩格式中,位图中每一个点的象素值对应于位图阵列的若干位,而位图阵列的大小由图
象的宽度、高度及图象的颜色数(biBitCount)决定。
4.1 图扫描行与位图阵列的关系
一个扫描行的象素值需要满足一定的要求,这种要求是每行的字节数必须为 4 的倍数,
不足的补零。若记录一个扫描行的象素值需要 n 个字节,那么公式为:
当(biWidth*biBitCount)能被 32 整除时;
n=(biWidth*biBItCount)/8。
当(biWidth*biBitCount)不能被 32 整除时;
n=((biWidth*biBitCount)/32)*4+4。
比实际图象多出的象素其值要用 0 表示。位图阵列的大小为 n*biHeight 字节,位图阵列
中第 0—n-1 个字节代表图象的第一个扫描的象素值,第 n—2n-1 个字节代表图象的第二
个行的象素值,…….,第(I-1)*n---I*n-1 个字节代表图象的第 I 个扫描行的象素值。
4.2 位图象素值与位图阵列的对应关系
以第 I 个扫描行为例,若第 I 扫描行的象素值的 n 个字节分别为:b0,b1….b(n-1),则根据
表示一个象素值所需位数的不同,对应关系如下:
当 biBtCount=1 时,一个象素值可用 1 比特(1 位)表示。b0 的第七位表示位图的第 I 扫
描行的第 1 个象素值,第 6 位表示第 2 个象素值,….,第 0 位表示第 8 个象素值;b1 第
7 位表示第 I 扫描行的第 9 个象素值, 第 6 位表示第 10 个象素值, 第 5 位表示第 11 个象
素值,….。
当 biBtCount=4 时,一个象素值可用 4 比特(4 位)表示。b0 的第 7--4 位表示位图的第 I
扫描行的第 1 个象素值,第 3--0 位表示第 2 个象素值, b1 第 7--4 位表示第 I 扫描行的
第 3 个象素值, 第 3--0 位表示第 4 个象素值,….。
当 biBtCount=8 时,一个象素值可用 8 比特(8 位)表示。b1 表示位图的第 I 扫描行的第
1 个象素值, b2 表示第 I 扫描行的第 2 个象素值,……。
当 biBtCount=24 时,一个象素值可用 24 比特(24 位)表示。b0,b1,b2 表示位图的第 I 扫
VC++项目二
描行的第 1 个象素值, b3,b4,b5 表示第 I 扫描行的第 2 个象素值,……。
二.获取教师机的屏幕图象
要实现软件屏幕共享功能,首先必须获取教师机的屏幕图象。对于 Windows 的每一个窗口
都可以看作是屏幕上的一个图象,该图象可以通过程序获得。Windows 的整个桌面也是一个窗
口,因此,我们可以获得对应整个桌面的图象。
1.获取教师机屏幕位图句柄
利用 CreateCompatibleDC、CreateCompatibleBitmap 及 BitBlt 等 API 函数能够比较容易地
抓取整个屏幕图像,但所抓取屏幕图像不包含当前光标区域。因此,我们必须先获取窗口
屏幕图象,然后获取当前的光标,并将光标画在先前获取的屏幕图象上,生成带光标的新
的图象。获取屏幕图象及光标的程序代码如下:
//获取全屏幕窗口的设备描述表
HDC hdcScreen=::GetDC(NULL);
//产生全屏幕窗口设备描述表的兼容设备描述表
m_hdcCompatible=CreateCompatibleDC(hdcScreen);
//产生全屏幕窗口设备描述表的兼容位图
HBITMAP m_hbmScreen=CreateCompatibleBitmap(hdcScreen,
GetDeviceCaps(hdcScreen,HORZRES),GetDeviceCaps(hdcScreen,VERTRES));
//将兼容位图选入兼容设备描述表
SelectObject(m_hdcCompatible,m_hbmScreen);
//将全屏幕窗口位图的象素数据拷贝到兼容设备描述表
BitBlt(m_hdcCompatible,0,0,GetDeviceCaps(hdcScreen,HORZRES),
GetDeviceCaps(hdcScreen,VERTRES),hdcScreen,0,0,SRCCOPY);
//获取当前光标及其位置
HCURSOR hCursor=GetCursor();
POINT ptCursor;
GetCursorPos(&ptCursor);
//获取光标的图标数据
ICONINFO IconInfo;
if (GetIconInfo(hCursor, &IconInfo))
{
ptCursor.x -= ((int) IconInfo.xHotspot);
ptCursor.y -= ((int) IconInfo.yHotspot);
if (IconInfo.hbmMask != NULL)
DeleteObject(IconInfo.hbmMask);
if (IconInfo.hbmColor != NULL)
DeleteObject(IconInfo.hbmColor);
}
//在兼容设备描述表上画出该光标
DrawIconEx(
m_hdcCompatible,
ptCursor.x, ptCursor.y,
hCursor,
0,0,
0,
cursor
NULL,
// handle to device context
// handle to icon to draw
// width of the icon
// index of frame in animated
// handle to background brush
VC++项目二
DI_NORMAL | DI_COMPAT
);
// icon-drawing flags
在上面的程序代码中,有几点需要特别注意:
1).每个设备描述表中都包含有一个位图,CreateCompatibleDC 返回的兼容设备描述表所
包含的位图大小仅为一个象素,并且是单色图像。
2).CreateCompatibleBitmap 返回的位图对象只包含相应设备表述表中的位图的位图信息
头,不包含颜色表和象素数据块。因此,选入该位图对象的设备描述表不能象选入普通位图对
象的设备描述表一样应用,必须在 SelectObject 函数之后,调用 BitBlt 将原始设备描述表的颜色
表及象素数据块拷贝到兼容设备描述表。
3).获取光标图像的程序代码中,GetCursorPos 所取回的坐标点是光标的中间点的位置,而
DrawIconEx 函数所要求的坐标值为光标的左上角坐标,因此,要调用 GetIconInfo 对坐标点进
行转换。
2.获取屏幕图象位图的位图信息及位图象素数据阵列
在上面程序代码中,我们已经获得了教师机屏幕位图图象的句柄,位图句柄本身并不包
含位图信息头和位图象素数据阵列。要在学生机上显示教师屏幕位图图象,必须将位图信息头
和位图象素数据阵列传送到学生机。调用 GetDIBits 函数可以获得位图句柄所对应的位图的信
息头和位图象素数据阵列,GetDIBits 函数的定义如下:
int GetDIBits(
// 设备描述表的句柄
// 位图句柄
HDC hdc,
HBITMAP hbmp,
// 从位图检索数据的起始行
UINT uStartScan,
// 准备检索的总行数
UINT cScanLines,
// 用于检索位图阵列数据的内存块
LPVOID lpvBits,
LPBITMAPINFO lpbi, // 用于检索位图信息头的内存块
UINT uUsage
//色彩表中的数据格式
);
如果 lpvBits 等于 NULL,该函数检索位图信息填充到 lpbi 的位图信息头部分。要检索出
lpvBits 的数据和 lpbi 中的色彩表,必须真确设置 lpbi 的位图信息头内容,所以,要获得位图的
信息头和位图象素数据阵列,必须调用两次 GetDIBits,第一次让 lpvBits 等于 NULL,以获得 lpbi
的位图信息头,第二次调用让 lpvBits 指向准备接收位图象素数据阵列的内存块,并将传入刚
才获得的 lpbi。程序代码如下:
PBITMAPINFO m_pbmi;//在类中定义的成员变量,以在其他函数中也能应用
m_pbmi=(PBITMAPINFO)LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER)+
256*sizeof(RGBQUAD));
m_pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
m_pbmi->bmiHeader.biBitCount = 0;
if(!GetDIBits(m_hdcCompatible,m_hbmScreen,0,1,NULL,m_pbmi,DIB_RGB_COLORS))
{
AfxMessageBox("GetDibits for BitampInfo error!",MB_OK,NULL);
Return;
}
LPBYTE lpBits;
lpBits= (LPBYTE) GlobalAlloc(GMEM_FIXED, m_pbmi->bmiHeader.biSizeImage);
if (!lpBits)
{
AfxMessageBox("Alloc memory error",NULL,NULL);
VC++项目二
Return;
}
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
if(!GetDIBits(m_hdcCompatible,m_hbmScreen,0,(WORD)(m_pbmi->bmiHeader.biHeigh,
lpBits, m_pbmi, DIB_RGB_COLORS))
{
AfxMessageBox("GetDibits for BitampInfo error!",NULL,NULL);
Return;
}
三.在学生机屏幕上显示教师屏幕图象
1. 根据从网络上接收到的教师机屏幕位图图象的色彩表创建一个逻辑调色板。
2. 创建一个屏幕大小的无边框窗口。
3. 将逻辑调色板选入该窗口设备描述表。
4. 调用 StrechDIBits 在学生机屏幕上输出教师机屏幕图象。
任何一种颜色都可由红(R)、绿(G)、蓝(B)三个基色组合而成,用 R,G,B 颜色分量来表示数字图象
象素的颜色值的方法就是 RGB 法。如用于表示 R,G,B 颜色分量的位数分为 n1,n2,n3,则可表示的象
素的颜色数为 2(n1+n2+n3)。如果分别用 8 位来表示三颜色分量,则总共需要 24 位来表示 RGB 三
色,可表示的颜色数为 2(24)=16,777,216。一个调色板是一个数组,其中包含当前输出设备(显卡、
打印机)能够显示和画出的颜色的所对应的数字,该数字由红、绿、蓝三个基色的分量值组成。
调色板主要用于输出设备能够产生许多颜色,但输出设备实际上在任何时候都只能输出其中的部
分颜色,例如,输出设备能够产生 512 种颜色,但在任何时候都只能输出其中的 256 种颜色,这
256 种颜色可以是这 512 种颜色中的任意组合。对于这样的设备,系统维护一个用于跟踪和管理当
前输出设备的颜色的系统调色板。应用程序不能直接访问系统调色板,而是由系统为每个设备描
述表安排一个缺省的调色板,对于刚才的例子,这个调色板就包含了 512 种颜色中的 256 种颜色,
应用程序可以使用这个缺省的调色板。应用程序也可以根据需要产生一个逻辑调色板,并把它安
排到某个特定的设备描述表。对于刚才的例子,当要显示一个 256 色的位图图象时,位图图象的
某些颜色不在确省的调色板中,我们就要产生一个逻辑调色板,让其包含位图图象中的 256 种颜
色,并把这个逻辑调色板安排到准备输出这个位图图象的设备描述表上。
CClientDC dc(m_pWnd);//m_pWnd 是用于显示教师机屏幕位图的窗口
CPalette lPalDIB;
LPLOGPALETTE lpPal;
HPALETTE hOldPal;
if (m_pbmi->bmiHeader.biBitCount <16)
{
lpPal=(LPLOGPALETTE)GlobalAlloc(GMEM_FIXED,sizeof(LOGPALETTE)+
sizeof(PALETTEENTRY)*(1<bmiHeader.biBitCount));
lpPal->palVersion=0x300;
lpPal->palNumEntries=1<bmiHeader.biBitCount ;
for(int i=0;ipalNumEntries;i++)
{
lpPal->palPalEntry[i].peRed=m_pbmi->bmiColors[i].rgbRed;
lpPal->palPalEntry[i].peGreen=m_pbmi->bmiColors[i].rgbGreen;
lpPal->palPalEntry[i].peBlue=m_pbmi->bmiColors[i].rgbBlue;
lpPal->palPalEntry[i].peFlags=0;
}
VC++项目二
lPalDIB.CreatePalette(lpPal);
hOldPal=SelectPalette(dc.m_hDC,(HPALETTE)(lPalDIB.m_hObject),TRUE);
RealizePalette(dc.m_hDC);
GlobalFree(lpPal);
SetStretchBltMode(dc.m_hDC,COLORONCOLOR);
StretchDIBits(dc.m_hDC,0,0,GetSystemMetrics(SM_CXSCREEN),
GetSystemMetrics(SM_CYSCREEN),0,0,m_pbmi->bmiHeader.biWidth,
abs(m_pbmi->bmiHeader.biHeight),m_pBits,
m_pbmi,DIB_RGB_COLORS,SRCCOPY);
if (m_pbmi->bmiHeader.biBitCount <16)
SelectPalette(dc.m_hDC,hOldPal,TRUE);
DeleteObject(lPalDIB.m_hObject);
}
{
}
在上面程序中,调用 StretchDIBits 而不是 SetDIBitsToDevice 将位图输出,是为了适应教师机屏幕
分辨率与学生机屏幕分辨率不同的情况,StretchDIBits 具有缩放原图象的功能,而 StretchDIBits
只能按原图象的大小显示图象。