开源WINVNC(一)
WINVNC是用bitmap格式保存桌面图像的,所以先补充下相关知识。
图像“像素”(Pixel):把影像放大数倍,会发现这些连续色调其实是由许多色彩相近的小方点所组成,这些小方点就是构成影像的最小单位“像素”(Pixel)。
图像分辨率:表示每一个方向上的像素数量,比如640X480,表示由640X480个像素组成。
DPI(Dot Per Inch):表示每英寸显示的像素数。图像显示清晰效果就看这个拉,一英寸等于25.4mm。
位/像素 (bpp:bits per pixel):既每个像素数据需要占用的bit数目,用来保存颜色。比如8位可得到256种颜色,16位可得到65,536种颜色,而24位可得到16,777,216种颜色。
view plain
//WINDOWS API:
int nFullWidth=GetSystemMetrics(SM_CXSCREEN); //获取x方向分辨率
int nFullHeight=GetSystemMetrics(SM_CYSCREEN); //获取y方向分辨率
BOOL EnumDisplaySettingsEx( //获取屏幕分辨率和bpp
LPCTSTR lpszDeviceName, // display device
DWORD iModeNum, // graphics mode
LPDEVMODE lpDevMode, // graphics mode settings
DWORD dwFlags // options);
LONG ChangeDisplaySettingsEx( //改变屏幕分辨率和颜色质量
LPCTSTR lpszDeviceName, // name of display device
LPDEVMODE lpDevMode, // graphics mode
HWND hwnd, // not used; must be NULL
DWORD dwflags, // graphics mode options
LPVOID lParam // video parameters (or NULL)
);
int GetDeviceCaps( //获取想要的设备信息,包括dpi等
HDC hdc, // handle to DC
int nIndex // index of capability
);
图像格式一般分为两种:映射图像和向量图像,bitmap就是映射图像。metafile是向量图像。
位映射图像用离散的像素来处理输出设备;而向量图像用笛卡尔座标系统来处理输出设备,其线条和填充对象能被个别拖移。现在大多数的图像输出设备是位映射设备,这包括视讯显示、点阵打印机、激光打印机和喷墨打印机。而笔式绘图机则是向量输出设备。
编辑bitmap大小一般就是复制或删除像素的某些行和列的像素。
view plain
//WINDOWS API:
BitBlt (hdcDst, xDst, yDst, cx, cy, hdcSrc, xSrc, ySrc, dwROP) ;//从称为「来源」的设备上下文中将一个矩形区的像素传输到称为「目的(destination)」的另一个设备上下文中相同大小的矩形区。来源和目的设备上下文可以相同而且bpp必须相同。、
StretchBlt ( hdcDst, xDst, yDst, cxDst, cyDst,hdcSrc, xSrc, ySrc, cxSrc, cySrc, dwROP) ;//在复制时拉伸或者压缩图像尺寸
int GetDIBits( HDC hdc, // handle to DC
HBITMAP hbmp, // handle to bitmap
UINT uStartScan, // first scan line to set
UINT cScanLines, // number of scan lines to copy
LPVOID lpvBits, // array for bitmap bits
LPBITMAPINFO lpbi, // bitmap data buffer
UINT uUsage // RGB or palette index
);
//读取bitmap数据到缓冲区lpvBits。既按扫描顺序一个一个的像素数据。
HDC CreateCompatibleDC(HDC hdc);//创建一个内存
DCHBITMAP CreateCompatibleBitmap(
HDC hdc, // handle to DC
int nWidth, // width of bitmap,
in pixels
int nHeight // height of bitmap,
in pixels
);
//创建设备环境兼容的位图。注意如果bitmap要做不同设备copy,hdc尽量不要用内存DC的。因为由CreateCompatibleBitmap函数创建的位图的颜色格式与由参数hdc标识的设备的颜色格式匹配。该位图可以选入任意一个与原设备兼容的内存设备环境中。由于内存设备环境允许彩色和单色两种位图。因此当指定的设备环境是内存设备环境时,由CreateCompatibleBitmap函数返回的位图格式不一定相同。然而为非内存设备环境创建的兼容位图通常拥有相同的颜色格式,并且使用与指定的设备环境一样的色彩调色板。
HBITMAP CreateDIBSection( HDC hdc, // handle to DC
CONST BITMAPINFO *pbmi, // bitmap data
UINT iUsage, // data type indicator
VOID **ppvBits, // bit values
HANDLE hSection, // handle to file mapping object
DWORD dwOffset // offset to bitmap bit values
);
//可以得到创建的图像在内存中的首地址,可以直接访问图像内存地址了,这样就不要用GetDIBits来获取数据了GdiFlush();
//让应用程序进入等待状态,直到所有待决的绘图操作完成为止。阻塞等屏幕绘画完。通过成批合并绘图操作命令,win32图形子系统(GDI)可改善绘图的性能。如调用一系列绘图命令,他们都返回布尔值(TRUE表示成功,零表示失败),就可将他们置于一个内部GDI队列里。此时,函数可以立即返回。随后,GDI子系统会执行这些待决的绘图命令。可考虑一种最常见的情况。在这种情况下,系统安装了一块显示卡。卡上自带图形处理器或加速器。画图的时候,GDI只需将图形命令简单的发送给显示卡,另其完成实际的操作。如果必须等待每个绘图命令都完成并返回,系统和应用程序的性能就会受到显示卡绘图速度的极大限制。所以在这个时候,GDI将绘图命令置于一个名为“批”(Batch)的队列里。这样一来,系统和应用程序就能继续运行,同时仍然让显示卡进行绘图操作。
//下面是几个很重要的数据结构
BITMAPFILEHEADER
BITMAPFILEHEADER结构包含关于类型,大小,布局设备无关的位图信息。
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
/*成员
bfType
声明文件类型,必须是BM。
bfSize
声明位图文件的大小,以字节为单位。
bfReserved1
保留,必须为0。
bfReserved2
保留,必须为0。
bfOffBits
声明偏移量,从BITMAPFILEHEADER结构开始到位图数据,以字节为单位。
备注
在设备无关文件中,BITMAPINFOHEADER结构紧随其后。
*/
//BITMAPINFOHEADER
//BITMAPINFOHEADER结构包含设备无关位图的大小以及色彩格式的信息。
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
/*成员
biSize
声明这个结构体所需要的字节数。
biWidth
声明位图的宽,以像素为单位。
biHeight
声明位图的高,以像素为单位。如果biHeight是正数,位图是一个自下而上的设备无关位图,起点在左下角。如果biHeight是负数,位图是一个自上而下的设备无关位图,起点在左上角。
如果biHeight是负数,指示一个自上而下的设备无关位图,biCompression就必须为BI_RGB或者BI_BITFIELDS。自上而下的设备无关位图不能被压缩。
biPlanes
为目标装置声明平面数,这个值必须是1。
biBitCount
声明每像素位数。BITMAPINFOHEADER的成员biBitCount确定了定义每一个像素需要的位数,以及位图所需颜色的最大值。这个成员必须是以下的值:
Value Meaning
0 JPEG或者PNG格式声明每像素位数
1 位图是单色的,包含两个颜色信息,位图数组中的每一位代表一个像素。如果这一位是0,那么这个点的颜色就是调色板中第一个RGBQUAD结构所定义的颜色。如果这一位是1,那么这个点的颜色就是调色板中第二个GBQUAD结构所定义的颜色。
4 位图最多有16种颜色。调色板数组中有16个元素。位图中的每一个像素由一个4位的索引指向调色板。举个例子,位图的第一个字节是0x1F,这个字节代表两个像素,第一个像素颜色是调色板中的第二个RGBQUAD结构所定义的颜色,第二个像素颜色是调色板中的第十六个RGBQUAD结构所定义的颜色。
8 位图最多有256种颜色。调色板中有256个元素。每一个字节代表一个颜色。
16 位图有2^16种颜色。如果biCompression为BI_RGB,则没有调色板。RGB565使用16位表示一个像素,这16位中的5位用于R,6位用于G,5位用于B。 RGB555是另一种16位的RGB格式,RGB分量都用5位表示(剩下的1位不用)。比如从高位到地位:X R R R R G G G G G B B B B B (X表示不用,可以忽略)。
可以组合使用屏蔽字和移位操作来得到RGB各分量的值:
#define RGB555_MASK_RED 0x7C00
#define RGB555_MASK_GREEN 0x03E0
#define RGB555_MASK_BLUE 0x001F
R = (wPixel & RGB555_MASK_RED) >> 10; // 取值范围0-31
G = (wPixel & RGB555_MASK_GREEN) >> 5; // 取值范围0-31
B = wPixel & RGB555_MASK_BLUE; // 取值范围0-31
24 位图最多有2^24种颜色。没有调色板。位图数组中每三个字节代表一个像素的红绿蓝相对强度。
biCompression
BI_RGB:对一种颜色进行编码的方法统称为“颜色空间”或“色域”。用最简单的话说,世界上任何一种颜色的“颜色空间”都可定义成一个固定的数字或变量。RGB(红、绿、蓝)只是众多颜色空间的一种。采用这种编码方法,每种颜色都可用三个变量来表示-红色绿色以及蓝色的强度。RGB1、RGB4、RGB8都是调色板类型的RGB格式,在描述这些媒体类型的格式细节时,通常会在BITMAPINFOHEADER数据结构后面跟着一个调色板(定义一系列颜色)。它们的图像数据并不是真正的颜色值,而是当前像素颜色值在调色板中的索引。以RGB1(2色位图)为例,比如它的调色板中定义的两种颜色值依次为0x000000(黑色)和0xFFFFFF(白色),那么图像数据001101010111…(每个像素用1位表示)表示对应各像素的颜色为:黑黑白白黑白黑白黑白白白…。
biSizeImage
声明图像文件的大小,以字节为单位。如果是BI_RGB位图,可能是0。
biXPelsPerMeter
声明横向点距,以像素每米为单位。
biYPelsPerMeter
声明纵向点距,以像素每米为单位。
biClrUsed
声明实际使用到的颜色个数,如果是0,则使用所有颜色。
biClrImportant
声明显示这个位图必须的颜色索引个数,如果是0,则需要所有颜色。
*/
//RGBQUAD
//RGBQUAD结构描述红绿蓝相对亮度
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
/*成员
rgbBlue
声明蓝色强度
rgbGreen
声明绿色强度
rgbRed
声明红色强度
rgbReserved
保留,必须是0
*/
//我们结合源码看下函数BOOL vncDesktop::SetPixShifts()就能更好的理解它们
view plain
struct _BMInfo {BOOL truecolour;BITMAPINFO bmi;// Colormap info - comes straight after BITMAPINFO - **HACK**RGBQUAD cmap[256];} m_bminfo;
view plain
BOOLvncDesktop::SetPixShifts()
{
// Sort out the colour shifts, etc.
DWORD redMask=0, blueMask=0, greenMask = 0;
switch (m_bminfo.bmi.bmiHeader.biBitCount)
{
case 16:
// Standard 16-bit display
if (m_bminfo.bmi.bmiHeader.biCompression == BI_RGB)
//需要自己来指定三色各占哪几位,把该色的所占位置1其它位置0,保存该值(暂时叫色标吧)。
//biBitCount位16,对于blue颜色来说0-4这5个位置1,得到blue色标blueMask = 0x001f,当然这里作了强制转换,因为blue为无符号32位
//如果一个无符号16位的像素数据uspixel,先要强转为无符号32位的uipixel,当然不转也无所谓的,因为对应的色标也必定从无符号16位强转获取的。
//我们要获得blue的索引值只要uipixel&blueMask就OK拉
//当然red的索引值还是需要移位下(uipixel&redMask)>>10
{
// each word single pixel 5-5-5
redMask = 0x7c00;
greenMask = 0x03e0;
blueMask = 0x001f;
}
else
{
if (m_bminfo.bmi.bmiHeader.biCompression == BI_BITFIELDS)//通过读取RGBQUAD来获取色标
{
redMask = *(DWORD *) &m_bminfo.bmi.bmiColors[0];
greenMask = *(DWORD *) &m_bminfo.bmi.bmiColors[1];
blueMask = *(DWORD *) &m_bminfo.bmi.bmiColors[2];
}
}
break;
case 32:// Standard 24/32 bit displays
if (m_bminfo.bmi.bmiHeader.biCompression == BI_RGB)//同上,区别是每个颜色占了8位拉 each word single pixel 8-8-8
{
redMask = 0xff0000;
greenMask = 0xff00;
blueMask = 0x00ff;
}
else
{
if (m_bminfo.bmi.bmiHeader.biCompression == BI_BITFIELDS)
{
//同上
redMask = *(DWORD *) &m_bminfo.bmi.bmiColors[0];
greenMask = *(DWORD *) &m_bminfo.bmi.bmiColors[1];
blueMask = *(DWORD *) &m_bminfo.bmi.bmiColors[2];
}
}
break;
default:// Other pixel formats are only valid if they're palette-based
if (m_bminfo.truecolour)
{
vnclog.Print(LL_INTERR, "unsupported truecolour pixel format for setpixshifts/n");
return FALSE;
}
return TRUE;
}
// Convert the data we just retrieved
MaskToMaxAndShift(redMask, m_scrinfo.format.redMax, m_scrinfo.format.redShift);
MaskToMaxAndShift(greenMask, m_scrinfo.format.greenMax, m_scrinfo.format.greenShift);
MaskToMaxAndShift(blueMask, m_scrinfo.format.blueMax, m_scrinfo.format.blueShift);
return TRUE;
}
看看函数最后调用的inline void vncDesktop::MaskToMaxAndShift(DWORD mask, CARD16 &max, CARD8 &shift)函数:
view plain
inline voidvncDesktop::MaskToMaxAndShift(DWORD mask, CARD16 &max, CARD8 &shift)
{
for (shift = 0; (mask & 1) == 0; shift++)
mask >>= 1;//无符号32位的mask右移操作,直到碰到位的值位1则退出。那么shift保留了mask从低位到高位数值连续为0的个数。
//把右移后的值牵制转换成无符号16位,赋值给max。
max = (CARD16) mask;
//结合调用MaskToMaxAndShift(redMask, m_scrinfo.format.redMax, m_scrinfo.format.redShift);来看
//那么m_scrinfo.format.redMax保存的就是red的最大值,m_scrinfo.format.redShift是色标到最大值的位移值
//假设一个无符号N(16或者32)位的像素数据upixel,
//那么red的值就是( uspixel & (m_scrinfo.format.redMax<<m_scrinfo.format.redShift) )>>shift
}
下篇再看看桌面包装类vncDesktop的其它函数。