GDI/GDI+ 绘制网站流量统计报表 总结(5)

来源:互联网 发布:卧底软件免费下载 编辑:程序博客网 时间:2024/06/02 17:42

第五讲GDI/GDI+高级编程进阶

在本讲中主要解决一下三个方面的内容

1.图片平铺

2.DC的偏移

3.保存图像文件到本地磁盘

 

在绘制背景图时,发现位图并没有铺满整个客户区,那么解决这个问题的办法有两个,一个位图拉伸,另一个是位图平铺,位图拉伸比较容易,调用StretchBlt函数就可以对位图进行拉伸拷贝,以适应客户区大小的改变。那么位图平铺如何实现?在第一讲中刚开始是采用BitBlt来将内存DC中的位图拷贝到目标DC上,采用的标志位是SRCCOPY, 因此位图以自己默认的大小显示并不会随窗口大小的改变而变大或缩小,那么是否可以同过计算客户区的大小和默认位图的大小,然后用恰当数量的位图来填充客户区呢? 显然这种方法是可行的,这个操作比第一讲中直接加载位图填充客户区来讲,相当于进行了多次填充, 因为第一讲中之所以没有铺满整个客户区是因为 位图只填充了一次,即不管是否要重绘窗口,都只填充了一张位图,那么现在是要计算客户区的大小,当大小发生改变时,填充的位图数也要跟着改变以保证能够全部铺满整个客户区。

首先要对客户区进行绘制,就需要获取其DC,那么这里我们通过GetDC()这个函数来获取,GetDC()中需要传入一个窗口句柄,以便知道获取的是哪个窗口的DC。那么现在获取了DC之后,就应该对其进行绘图等操作了, 但是在操作之前一定要记得先保存当前系统默认的DC的各种属性,以便操作完了之后恢复回来,从而不影响其它调用系统默认DC属性的方法。那么为了简单起见,这里采用SaveDC来对DC中的所有属性一次性保存。

具体操作代码如下:

//获取窗口DC

HDC hdc =GetDC(hWnd);

//保存DC状态

int isdc =SaveDC(hdc);

 

我们要做的操作是将位图铺满整个客户区, 那么就要把位图加载到内存中来, 通过调用LoadBitmap将位图加载进来,并保存返回的位图句柄。LoadBitmap原型如下:

HBITMAP LoadBitmap(

HINSTANCE hInstance,//handle to application instance

LPCTSTR lpBitmapName//name of bitmap resource

);

具体加载代码:

HBITMAP hBitMap =::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));

位图加载到内存中之后还不能直接绘制到目标DC上去, 就相当于打印东西一样,比如打印东西的流程如下:

第一步把待打印的文档拷贝到U盘上,

第二步把U盘里的东西拷贝到电脑上,

第三步将电脑上的这个文档的内容通过打印程序输出到打印机中的A4上

那么把位图加载到内存就相当于第一步把文档拷贝到U盘中

那么为了打印这个文档, 则第二步就是要文档拷贝到电脑中,要拷贝到电脑上,首先就要有一个电脑,那么位图要绘制到目标DC上,就要先拷贝到内存DC上,要拷贝到内存DC上就要有一个内存DC, 所以先创建一个内存DC

HDC hMemDC =CreateCompatibleDC(hdc);

之后将东西拷贝到内存DC中来,即将位图从内存中选入到内存DC中来

SelectObject(hMemDC,hBitmap);

我们这次是要将位图以平铺的方式铺满整个客户区,那么就要知道这位图和和这个客户区的大小,以确定是用一张位图可以铺满还是要N张才能铺满,那么下面就要计算位图的大小和客户区的大小。

首先声明一个位图变量, 通过调用GetObject()来获取位图的信息

BITMAP bitmap;

::GetObject(hBitmap,sizeof(BITMAP),&bitmap);

获取了位图信息之后,就要来获取客户区大小

RECT rect;

::GetClientRect(hdc,&rect);

但是这里我们先不去完成整个客户区的背景绘制,而是指定一个区域去平铺填充,因为前面已经通过拉伸的方式实现了这个功能,如果再用平铺来做这个功能则需要先屏蔽之前的拉伸代码。那么这里指定一个300长,300宽的矩形区域,让位图来填充。那么就需要计算这个区域要几张位图才能填充得满?

先来计算一下,一行需要几张位图才能填得满

int iNumX =300/bitmap.bmWidth +1; 这里应该很好理解,300/bitmap.bmWidth 就是一行填入几张位图, 加1个像素 是我们好绘制一个矩形来检验它是否刚好填充这个指定的矩形框,同样计算一列需要填入几张位图

int iNumY =300/bitmap.bmHeight +1;

现在图片本身的大小已经计算出来, 指定区域需要几张位图也已经计算完毕, 之前把位图也是选入了内存DC中来了, 那么现在的内存DC就成了源DC, 然后就需要把源DC中的位图,分别贴到目标DC上的指定区域里面去

for(inti=0;i<iNumY; ++i)

for(int j = 0;j<iNumX; ++j)

    BitBlt(hdc,j*bitmap.bmWidth,i*bitmap.bmHeight,bitmap.bmWidth,bmp.bmHeight,hMemDC,0,0,SRCCOPY);

好,现在在指定的矩形区域内已经把位图贴上去了,那怎么看出来是贴在指定的矩形中呢? 我们画一个这么大小的矩形框 帮助检验就可以了,画边框是用画笔来绘制, 那么先创建一个画笔, 然后选入当前DC ,然后调用Rectangle绘制, 销毁相应的句柄,就可以了

HPEN hPenRed =::CretePen(PS_SOLID , 1, RGB(255,0,0));

::SelectObject(hdc,hPenRed);

绘制矩形框

Rectangle(hdc,0,0,300,300);

 

::DeleteObject(hPenRed);

::DeleteObject(hBitmap);

::DeleteObject(hMemDC);

 

用完之后要恢复DC

RestoreDC(hdc,isdc);

注意前面我们通过GetDC获得了一个DC, 现在不需要了的时候一定记得要用ReleaseDC来释放掉

ReleaseDC(hWnd,hdc);

F5 运行,结果发现矩形框中被系统默认的白色画刷填充了,那么在矩形绘制之前应该选择一个空画刷选入到当前DC

SelectObject(hdc,GetStockObject(NULL_BRUSH));

F5 运行, 可以看到矩形中填充的背景位图了,但是发现位图填充超出了矩形框, 但是我们可能不需要它在矩形框外面还显示 ,这时候该怎么解决呢? 这里就需要引入减区的概念了。

就是对DC进行剪辑

用SelectClipRgn函数来剪辑指定的区域,只有被剪辑的部分,才会被绘制,原型如下:

SelectClipRgn(HDChDC,HRGN hRgn);

参数hDC就是我们要剪辑的DC句柄。

参数hRgn就是我们要剪辑的区域。

要剪辑的区域,不一定要是矩形区域,也可以是圆角矩形区域,也可以是圆形区域等。 比如:

CreateRectRgn(intx1, int y1, int x2, int y2);就是创建矩形区域,

CreateRoundRectRgn(intx1, int y1, int x2, int y2,int w ,int h)就是创建圆角矩形区域。

CreateElipticRgn(intx1, int y2, int x2, int y2);就是创建圆形区域。

 

那么我们现在怎么来修改之前的代码 使位图不贴出矩形框外去呢?

其实就是在BitBlt贴图之前,我们先用HRGN 创建一个矩形区域,然后通过SelectClipRgn来将这个矩形区域选入当DC就可以了, 这样在当前DC上所的绘制 只有在矩形区域里内容才会被显示, 其它地方的内容将不会被显示, 直达会DC为止。

具体操作代码如下:

HRGN hRgn =CreateRectRgn(0,0,300,300);

SelectClipRgn(hdc,hRgn);

for(inti=0;i<iNumY;++i)

……

这样即可。那么上面的平铺方式固然是一种思路, 那么还有没有另外的方法可以实现这个平铺功能呢?上面是通过计算矩形区域中需要多少张位图来填充,然后填入多少张位图来解决这个问题, 其实不用这么麻烦, 可以直接通过创建位图画刷来完成, 因为位图画刷本身支持平铺,所以这样做就不需要计算客户区和加载位图的大小了

操作方式同样也是:

1.通过LoadBitmap()加载位图

2.调用CreatepatternBrush()根据加载的位图创建纹理画刷

3.将画刷选入当前DC

4.为了观察我们创建一支红色画笔

5.然后调用Rectangle来绘制矩形

6.善后清理工作

具体代码如下;

HBITMAP hBitmap =LoadBitmap(hInst,MAKEINTRESOURCE(IDB_BITMAP1));

HBRUSH hbr=CreatePatternBrush(hBitmap);

SelectObject(hdc,hbr);

HPEN hPenRed =CreateRed(PS_SOLID,1,RGB(255,0,0));

SelectObject(hdc,hPenRed);

Rectangle(hdc,0,0,300,300);

::DeleteObject(hPenRed);

::DeleteObject(hBitmap);

DeleteObject(hbr);

RestoreDC(hdc,isdc);

好完成了位图平铺之后,下面来说说DC偏移的概念,比如原来写了很多代码,但是它们都是基于0,0那个坐标的,可能有时候我们觉得坐标不应该从0,0出发,要去修改DC里面函数里的值,可能修改不了,一个是可能命令比较多,修改了一个其它的都要修改,二个是有时候DC绘图这些代码不是我们本身写的,无法去修改里面的代码,比如别人动态库里的绘图是针对左上角的,但是你现在要偏移一下,但是无法修改源代码,那此时该怎么办?此时可以通过修改画布的视口原点,就可以改变它的值。这里可以调用SetViewportOrgEx来使得原来的原点偏移到想要的位置,比如原来的原点是0,0,现在调用 SetViewportOrgEx(hdc,300,300,NULL); 那么现在的原点就变成了300,300了。 它的原型如下:

BOOLSetViewportOrgEx(

HDC hdc, //handle todevice context

int x, //newx-coordinate of viewport origin

int y, // newy-coordinate of viewport origin

LPPOINT lpPoint //original viewport origin

);

lpPoint就是返回之前的原点。

也可以通过GetViewportOrgEx(HDChdc, LPPOINT lppoint);来获取当前绘制的起点的坐标

参数hdc 就是要获取绘制起点坐标的DC句柄

参数lppoint用来获取hdc当前绘制的起点坐标。

 

好图像绘制之后通常我们需要把它保存起来。那怎么保存图像呢?我们这里保存为BMP文件,那么首先来了解一下Bitmap的结构信息:

BMP 文件总体上由4部分组成,分别是位图文件头位图信息头调色板图像数据

位图文件头包含了图像类型,图像大小,图像数据存放地址和两个保留未使用的字段。原型如下:

typedef structtagBITMAPFILEHEADER{

WORD bfType; //图像类型

DWORD bfSize; //图像大小

WORD bfResereved1;//保留未使用的字段

WORD bfReserved2; //保留未使用的字段

DWORD bfOffBits; //图像数据存放地址

}BITMAPFILEHEADER;

 

位图信息头包含了位图信息头的大小,图像的宽高,图像的色深,压缩说明,图像数据的大小和其它一些参数。

typedef struct tagBITMAPINFOHEADER{

DWORD biSize; //本结构的大小

LONG biWidth; //BMP图像的宽度

LONG biHeight; BMP图像的高度

WORD biPlanes; //图像的面数

WORD biBitCount; //图像的色深,即一个像素用多少位表示。

DWORD biCompression; //压缩方式,如0表示不压缩,1表示RLE8压缩,2表示RLE4压缩。

DWORD biSizeImage; //BMP图像数据的大小。

LONG biXPelsperMeter; // 水平分辨率

LONG biYPelsperMeter; //垂直分辨率

DWORD biClrUsed; //BMP图像使用的颜色,0表示使用全部颜色,对于256位图来说,此值为256.

DWORD biClrImportant; //重要的颜色数,此值为0时所有颜色都重要,对于使用调色板的BMP图像来说,当显卡不能够显示所有颜色时,此值将被辅助驱动程序显示颜色。

}BITMAPINFOHEADER;

 

调色板是单色,16色和256色图像文件所特有的,相对应的调色板大小是2、16和256,调色板以4字节为单位,每4字节存放一个颜色值,图像的数据是指向调色板的索引。

调色板是用来解决色彩不丰满的时候用的。

调色板的数据结构定义:

typedef struct tagRGBQUAD{

BYTE rgbBlue; //蓝色值

BYTE rgbGreen;

BYTE rgbRed;

BYTE rgbReserved;

}RGBQUAD;

 

位图数据:如果图像是单色、16色和256色,则紧跟着调色板的是位图数据,位图数据是指向调色板的索引序号。

如果图像是16位,24位和32位色,则文件中不保留调色板,即不存在调色板,图像的颜色直接在位图数据中给出。

16位图像使用2字节保存颜色值,常见有两种格式:5位红5位绿5位蓝和5位红6位绿5位蓝,即555格式和565格式。555格式只使用了15位,最后一位保留,设为0.

24位图像使用3字节保存颜色值,每一个字节代表一种颜色,按红,绿,蓝排列。

32位图像使用4字节保存颜色值,每一个字节代表一种颜色,除了原来的红、绿、蓝,还有Alpha通道,即透明色。

 

那么我们这里是要保存一个24位个BMP图像文件,所以我们先设置每个像素所占字节数为24的变量

WORD wBitCount = 24;

然后定义位图中像素字节的大小,位图文件的大小,写入的文件字节数这里都初始化为0

DWORD dwBmBitSize= 0, dwDIBSize=0,dwWritten =0;

接下来声明位图属性结构

BITMAP Bitmap;

声明位图文件头结构

BITMAPFILEHEADER bmfHdr;

声明位图信息头结构

BITMAPINFOHEADER bi;

声明指向位图信息头结构的指针

LPBITMAPINFOHEADER lpbi;

声明一个文件句柄,和一个内存句柄

HANDLE fh,hDib;

然后计算位图文件每个像素所占的字节数

hDC = GetDC(NULL);

GetObject(hBitmap,sizeof(Bitmap),(LPSTR)&Bitmap);

bi.biSize =sizeof(BITMAPINFOHEADER);

bi.biWidth =Bitmap.bmWidth;

bi.biHeight =Bitmap.bmHeight;

bi.biPlanes  =1;

bi.biBitCount =wBitCount;

bi.biCompression =BI_RGB;

bi.biSizeImage =0;

bi.biXPelsPerMeter =0;

bi.biYPelsPerMeter =0;

bi.biClrImportant=0;

bi.biClrUsed = 0;

 

dwBmBitSize =((Bitmap.bmWidth*wBitCount+31)/32)*4*Bitmap.bmHeight;

 

hDib =GlobalAlloc(GHND,dwBmBitSize + sizeof(BITMAPINFOHEADER));

lpbi =(LPBITMAPINFOHEADER)GlobalLock(hDib);

*lpbi = bi;

 

GetDIBits(hDC,hBitmap,0,(UINT)Bitmap.bmHeight,(LPSTR)lpbi+sizeof(BITMAPINFOHEADER),(BITMAPINFO*)lpbi,DIB_RGB_COLORS);

ReleaseDC(NULL,hDC);

fh =  CreateFileA(L"C:\\Image.bmp",GENERIC_WRITE,0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL |FILE_FLAG_SEQUENTIAL_SCAN, NULL);

 

if(INVALID_HANDLE_VALUE == fh)

{

ShowErrMsg();

return FALSE;

}

 

 

//设置位图文件头

bmfHdr.bfType =0x4D42; //"BM"

dwDIBSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+dwBmBitSize;

bmfHdr.bfSize =dwDIBSize;

bmfHdr.bfReserved1=0;

bmfHdr.bfReserved2=0;

bmfHdr.bfOffBits =(DWORD)sizeof(BITMAPFILEHEADER)+

(DWORD)sizeof(BITMAPINFOHEADER);

//写入位图文件头 WriteFile(fh,(LPSTR)&bmfHdr,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);

 

//写入位图文件其余内容

WriteFile(fh,(LPSTR)lpbi,dwDIBSize,&dwWritten,NULL);

 

//清除

GlobalUnlock(hDib);

GlobalFree(hDib);

CloseHandle(fh);

 

F5运行 到C盘下去, 怎么没有保存的Image.bmp文件呢? 怎么回事呢? 也没报错,没有什么错误提示,怎么会没有保存呢? 把C盘改成其他盘就可以看到保存了文件了,那这是怎么回事呢? 没关系我们可以通过GetLastError()函数和FormatMessage函数来获取其相应的错误信息。 具体代码如下:

void ShowErrMsg()

{

    TCHAR szBuf[80];

    LPVOID lpMsgBuf;

    DWORD dw = GetLastError();

    FormatMessage(

        FORMAT_MESSAGE_ALLOCATE_BUFFER |

        FORMAT_MESSAGE_FROM_SYSTEM ,

        NULL,

        dw,

        MAKELANGID(LANG_NEUTRAL,

        SUBLANG_DEFAULT

        ),

        (LPTSTR) &lpMsgBuf,

        0, NULL );

     wsprintf(szBuf, _T("%sfailed: GetLastError returned %u\n"),

       lpMsgBuf, dw);

     MessageBox(NULL, (LPCWSTR)szBuf, _T("Error"), MB_OK);

    LocalFree(lpMsgBuf);

  }

 

 

然后我们调用这个错误代码, 发现是 错误代码为5 表示拒绝访问? 哦是因为没有权限所以没法写入到C盘, 解决方案是 右键项目属性,链接器 选择 清单文件 选择 UAC 执行级别 选择requireAdministrator  然后保存 然后F5执行, C盘中就看到保存了的文件了。


//Start//////////平铺方式A//////////////*int isdcfa = SaveDC(hdc);HBITMAP hBitmapfa = ::LoadBitmapW(hInst,MAKEINTRESOURCE(IDB_BITMAP1));HDC hMemDCfa = ::CreateCompatibleDC(hdc);SelectObject(hMemDCfa, hBitmapfa);BITMAP bmpfa;::GetObjectA(hBitmapfa,sizeof(BITMAP),&bmpfa);int iNumX = 300/bmpfa.bmWidth+1;int iNumY = 300/bmpfa.bmHeight+1;HRGN hRgn =::CreateRectRgn(0,0,300,300);SelectClipRgn(hdc,hRgn);for(int i=0; i<iNumY;++i)for(int j=0;j<iNumX; ++j)BitBlt(hdc,j*bmpfa.bmWidth,i*bmpfa.bmHeight,bmpfa.bmWidth,bmpfa.bmHeight,hMemDCfa,0,0,SRCCOPY);HPEN hPenRed = ::CreatePen(PS_SOLID,1,RGB(255,0,0));SelectObject(hdc,hPenRed);SelectObject(hdc,GetStockObject(NULL_BRUSH));Rectangle(hdc,0,0,300,300);::DeleteObject(hPenRed);::DeleteObject(hMemDCfa);::DeleteObject(hBitmapfa);RestoreDC(hdc,isdcfa);*///End//////////平铺方式A///////////////Start//////////位图画刷平铺方式B/////////////int isdc = SaveDC(hdc);::SetViewportOrgEx(hdc,800,50,NULL);HBITMAP hBitmap = ::LoadBitmapW(hInst, MAKEINTRESOURCE(IDB_BITMAP1));HBRUSH hbrfb = ::CreatePatternBrush(hBitmap);SelectObject(hdc,hbrfb);HPEN hPenRed = CreatePen(PS_SOLID,1,RGB(255,0,0));SelectObject(hdc,hPenRed);Rectangle(hdc,0,0,300,300);::DeleteObject(hBitmap);::DeleteObject(hbrfb);::DeleteObject(hPenRed);RestoreDC(hdc,isdc);//End//////////位图画刷平铺方式B/////////////



原创粉丝点击