Chinaunix首页 | 论坛 | 博客
  • 博客访问: 175276
  • 博文数量: 65
  • 博客积分: 1790
  • 博客等级: 上尉
  • 技术积分: 460
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-21 23:51
文章分类
文章存档

2012年(8)

2011年(38)

2010年(19)

分类: Python/Ruby

2011-02-25 00:01:16

1.1. 起因

原以为用不到swig了。以前没有专门看过,只做过个小例子。后来不玩不玩又都忘了。前几天玩pyopengl, 发现一个很奇怪的事情。原始的意图是准备不开窗口,直接在一张位图中离屏渲染3D图形。可惜OpenGL中没有定义标准的离屏渲染函数,离屏渲染的工作被 扔到各个系统中分别实现。OpenGL有几种主要实现,在X系统上是GLX,在windows系统上是WGL,在Apple上是AGL,它们各自都有支持 离屏渲染的API,而且各个系统不一样。OS2上的OpenGL实现PGL仿佛不叫离屏渲染,叫“获得上个缓冲区”,大概和离屏渲染差不多。OS2我大概 八辈子用不到了。主要看看GLX和WGL大概差不多了。GLX一支持,一大片的系统都支持了。够了。但是着手在WGL的试验中,我发现一个很奇怪的问题。 用C++的代码写出的函数没问题。但是仿写成python总是会出现莫名错误。也不知道到底是什么原因,哪块出了问题也实在不好找(因为涉及到 pyopengl,pywin32,PIL3个库,PIL可能在这里不是必要的)。后来一想,算了,既然C++不会错,我就包装一个C++的库好了。把离 屏渲染的准备工作和扫尾工作放到库中,用Python调用这个库进行初始化离屏渲染的上下文环境和删除上下文环境的工作。具体的OpenGL绘制放到 Python中调用(或者全部放到C++中,由Python计算控制视点变化和状态改变?)

1.2. 一个最简单的绑定

既然考虑到用绑定了,问题就来了。绑定到底怎么玩?虽然以前玩过,但是一知半解,只是随便玩玩,现在却一点也想不起来怎么弄的了。绑定C++到Python有很多的选择,到底用哪种?

我想起GDAL。这个库就是C++写的,但是通过swig绑定到了Python。现在我都用Python来调用GDAL了。是否可以参考它呢?

研究了一下,发现GDAL的Python绑定比较庞大(本来就比较庞大,它一个库就包含了一大堆的子库, 通过静态和动态链接搞在一起,形成一个大库)。研究了一个下午,终于有点懂了(其实可以借鉴一下pyshapelib这样比较小的库,结构比较简单)。它 用到的主要是两个东西。一个swig,一个是标准库distutils,swig用来绑定,distutils用来统一编译和安装。

既然用到了这两个东西,我们就来研究一下吧。

学习swig可以到,里面的Document有很多文档,比较可看的有和,还有一个,不过通过一个下午的研究发现可以不用太花力气在swig的使用和编译链接上面。swig主要要学的只是“.i”文件的写法,其他编译啊什么的都可以放到setup.py中,不过这就是distutils的任务了。

学习distutils直接就进入中察看。是教如何写setup.py文件的。它教了我们如何写setup.py文件,然后把我们的模块编译打包。现在仿佛应用最广的就是这种方式。

废话不多说。开始吧!我们做个最简单的。

首先要建立一个库,这一定是最先要做的。因为绑定绑定,首先要有个东西来绑,没有要绑的东西,还叫什么绑定!

建库方法可以随便,只要能生成动态链接库就Ok(swig有两种绑定方式,一种静态链接,一种动态链接。 静态链接是直接链接到Python核心中去,成为内建模块形式,这一般是不用的,现在又不是非常时期,不需要打入敌人内部:)。一种是动态链接,形成一个 模块,然后用的时候用import导入,这是一种我们最熟悉的形式,而大多数的情况下都是这么做的)。

我在windows下测试,所以我用vs建立了一个dll工程。我不是一个狂热的控制台一族,我还是喜欢 IDE形式的。当然你如果在linux下,可以用anjuta,kdevelop同样建立一个工程,或者你喜欢单用一个文本工具一个字一个字得敲以显示超 牛的技术风格,没问题。我们只要能生成库就好。下面说明的都是在windows下的试验过程。

建立成dll动态链接库工程TestModlue,然后把什么预编译头编译开关去掉,删掉那个什么stdafx.h 和 stdafx.cpp,把TestModule.cpp清空。

在TestModule.cpp中写几个函数吧!

#include
using namespace std;

void __declspec(dllexport)PrintHello(void)
{
cout<<"helloworld"<}

int __declspec(dllexport)Add(int i,int k)
{
return i+k;
}

好了,定义了两个函数,一 个打印“helloworld”一个做加法,这里需要注意的是_ _ declspec(dllexport)。这是函数导出的标志。一定要加这个。不然链接不会出现lib文件。函数导出有两种方法,一种是这个,一种是 def模块定义文件,我用前一种只是因为我习惯用那个。不过看起来def的定义方式更容易后期修改。你可以试一试。

好了,生成工程。我有一步修改工程属性的步骤。修改了生成dll和lib的路径和名称。默认的路径在 Debug目录下或者在release目录下,名称和工程名相同。同名的dll到后面和swig生成的py文件有冲突。所以我把生成目录转移到工程目录 下,把lib的名称改成TestModuleD.lib,把dll名称改成TestModuleDCpp.dll,之所以转移目录是为了后面指定目录路径 不会太深。在工程名后面加D说明是Debug版本,为了和Release版本区分,你也可以不加,不过后面指定lib的名称时需要注意。dll文件名加 Cpp后缀是为了和py文件定义的模块区分。

这样我们就有了可以使用的C++库了。

为了把C++绑定到python,要写一个.i文件。跳出TestModule文件夹,建立一个TestModule.i的文件,然后写一下给swig用来转化的接口。

%module TestModule

%{
#include
using namespace std;

void PrintHello(void);
int Add(int i,int k);
%}

void PrintHello(void);
int Add(int i,int k);

还是很简单的。%module后面跟模块名。下面%{ %}中间的内容是需要包含的头文件和函数定义。下面是函数的定义声明。

现在可以用swig生成绑定的cpp文件了,不过等等,不要像swig教程那样编译链接,虽然可以这样做,但是我们有distutils了!有炮不用,用鸟枪?

要用distutils,就要建立一个setup.py文件。这个有这个文件,我们就可以简单地使用 python setup.py build 和python setup.py install来编译链接,并且安装模块到python系统库中。一切其他的事情都不用你管。

建立一个setup.py。

写setup.py 其实很简单,需要的就只有一行(其他别看那么多,全部都是服务于那一行)。

最简单的setup.py 看,不过这还不是最简单的,最简单的是这样!

from distutils.core import setup

setup(name='TestModule wrap',
version='1.0',
ext_modules=[],
)

当然这样什么用都没有。

我们要编译一个模块,就要在setup中的ext_modules参数的列表中添加模块定义,这个模块定义是需要用到Extension类的。模块的定义看起来像这样:

TestModule_module = Extension('_TestModule',
sources = ['TestModule_wrap.cpp'],
include_dirs = [],
libraries = ['TestModuleD'],
library_dirs = ['./TestModule/'],
extra_link_args = [],
)

第一个参数是模块名,这 里是编译出来后的模块名,这个模块名最好不要和原来的模块同名,一般是在前面加个"_",但是也不要把模块名乱改,不然生成的绑定cpp链接后init方 法认不到。sources参数是指定要编译的源文件。这个注意,源文件是经过swig转化后的cpp文件,一般是模块名加个后缀_wrap。 include_dirs是指定编译时需要额外包含头文件的路径。libraries是指定要链接时需要用来链接的库,这里因为需要链接c++的库,所以 要将TestModuleD.lib链接进来,这里因为上面在编译链接时有改lib的名称,所以这里用的是TestModuleD,如果你没有改,就要用 TestModule,反正要跟lib去掉扩展名后的名称一样。library_dirs指定查找libraries中库所在的路径。最后一个是而外链接 标记,不管它。

最后经过整理,排版,最后的setup.py是这样:

from distutils.core import setup,Extension
include_dirs = []
libraries = ['TestModuleD']
library_dirs = ['./TestModule/']
extra_link_args = []

TestModule_module = Extension('_TestModule',
sources = ['TestModule_wrap.cpp'],
include_dirs = include_dirs,
libraries = libraries,
library_dirs = library_dirs,
extra_link_args = extra_link_args,
)
setup(name='TestModuleD wrapper',
version='1.0',
#py_modules=["TestModule"],
ext_modules=[TestModule_module],
)

好了。所有工作做完,现在编译链接。

一般来说,编译链接的工作放到一个makefile中进行,不过这里要进行的工作很少,不用那么兴师动众吧!写个批处理文件就好。

建立一个runsetup.bat,输入下列内容:

@echo off
swig -c++ -python -modern -new_repr -I. -o TestModule_wrap.cpp TestModule.i
python.exe setup.py build
pause

这里看到只作了两件事,一个是swig生成绑定的cpp,一个是用setup.py 进行编译。

好了,运行这个runsetup.bat吧!

running build
running build_ext
building '_TestModule' extension
D:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin\cl.exe /c /nologo /Ox
/MD /W3 /GX /DNDEBUG -Id:\python24\include -Id:\python24\PC /TpTestModule_wrap.
cpp /Fobuild\temp.win32-2.4\Release\TestModule_wrap.obj
TestModule_wrap.cpp
D:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin\link.exe /DLL /nologo
/INCREMENTAL:NO /LIBPATH:./TestModule/ /LIBPATH:d:\python24\libs /LIBPATH:d:\py
thon24\PCBuild TestModuleD.lib /EXPORT:init_TestModule build\temp.win32-2.4\Rele
ase\TestModule_wrap.obj /OUT:build\lib.win32-2.4\_TestModule.pyd /IMPLIB:build\t
emp.win32-2.4\Release\_TestModule.lib
正在创建库 build\temp.win32-2.4\Release\_TestModule.lib 和对象 build\temp.win
32-2.4\Release\_TestModule.exp
请按任意键继续. . .

好了生成成功!我们可以看到生成一个build文件夹,生成的东西放在build文件下的lib.win32-2.4目录下,是一个_TestModule.pyd的文件。

现在我们试试看python能否正确调用:

建立一个bin文件夹,把生成的_TestModule.pyd文件和TestModule.py文件还有那个C++生成的TestModuleDCpp.dll拷贝进来,然后在这个bin目录下运行python交互解释器。

>>> import TestModule
>>> dir(TestModule)
['Add', 'PrintHello', '_TestModule', '__builtins__', '__doc__', '__file__', '__n
ame__', '_newclass', '_object', '_swig_getattr', '_swig_repr', '_swig_setattr',
'_swig_setattr_nondynamic', '_swig_setattr_nondynamic_method', 'new', 'new_insta
ncemethod']
>>> TestModule.PrintHello()
helloworld
>>> TestModule.Add(1,100)
101
>>> TestModule.Add(77,100)
177
>>>

It work!运行正常!

不过可以看到dir(TestModule)会有一堆乱七八糟的东西出来。这是怎么回事?

>>> import _TestModule
>>> dir(_TestModule)
['Add', 'PrintHello', '__doc__', '__file__', '__name__']
>>> _TestModule.Add(77,100)
177
>>> _TestModule.PrintHello()
helloworld
>>>

试试这个!果然清爽很多。

因为生成TestModule是swig的工作,而真正的绑定是在_TestModule中做的。我们完 全可以不要swig生成的py文件,自己写一个。导入_TestModule中所有的原生态的东西。甚至可以对_TestModule进行包装!正像 GDAL中做的一样,_gdal.dll只是包装了c的绑定,gdal.py没有采用swig生成的那个,而是自己写了一个,在新的gdal.py中对 _gdal.dll中的C函数进行包装,形成C++类。而不是直接在Swig的i文件中包装成python类。

 

1. 开始我的GL离屏渲染绑定

呵呵,有了第一次的经验,我们就要开始我们的GL离屏渲染的绑定了。

关于OpenGL的离屏渲染,前面已经有一些涉及了。再说一下吧,OpenGL有两种渲染方式:一种是通 过操作系统打开窗口进行渲染,然后可以直接在屏幕上显示,这种渲染方式叫做屏幕渲染。一种通过在内存中一块位图区域内渲染,这种渲染方式在没有通过 SwapBuffer方式前不可以在屏幕上显示,所以这种方法叫离屏渲染。一般来说,OpenGL通过屏幕显示方式展示它的魅力,屏幕渲染方式是大多数情 况下的首选。而且很多窗口系统都有实现OpenGL的屏幕渲染方式。比如glut,wxwidgets,QT,gtk。但是有些时候我们不需要屏幕显示。 只是想把它保存成一个图像文件就好。而且我们就是不想打开一个窗口。这样就需要用离屏渲染的方法。在内存中画,最后保存成图像。

可惜的是OpenGL没有统一的离屏渲染操作API。GL把这些事情全部交给系统。这样就导致各个系统的 离屏渲染都有各自的API,Windows,X,Apple,SGI,OS2都有自己的离屏RC上下文构建方法,每套API都不同。在缺少了榜样的力量 后,各个系统就纷纷开始诸侯割据了,就造成天下大乱的局势。这样确实不太好。不过现在乱了就让它乱吧,谁叫我们是“小程序员”?天下大势就这样,你要怎么 着吧-_-! 没办法。实在是没办法~~~如今的世界太疯狂…… 如今的世界变化快……

我还是静下心来看看这么在各个系统上实现离屏渲染吧。OS2大概八辈子用不到了吧,Apple是高高在上的贵族们的东西。咱们老百姓……还是算了吧。老老实实研究一下Windows和X吧。于是先开始研究WGL。

WGL要建立离屏渲染,可以参看,不过太多,太乱了,中的解释比较浅显。也有两句解释(不过这里主要是SIG的解释,X的解释也比较详细)。最令人激动的是有win32上的完整例子。

简单得说吧,要进行离屏渲染,win32下需要做下面的几个步骤:

  1. 创建一个内存 DC
  2. 创建一个位图
  3. 把位图选入DC
  4. 设置DC的像元格式
  5. 通过DC创建OpenGL的渲染上下文RC
  6. 开始渲染.

好了,可用的渲染过程如下:

#include "stdafx.h"
#include
#include
#include
#include
#include
#include

using namespace std;

BOOL SaveBmp(HBITMAP hBitmap, string FileName)
{
HDC hDC;
//当前分辨率下每象素所占字节数
int iBits;
//位图中每象素所占字节数
WORD wBitCount;
//定义调色板大小, 位图中像素字节大小 ,位图文件大小 , 写入文件字节数
DWORD dwPaletteSize=0, dwBmBitsSize=0, dwDIBSize=0, dwWritten=0;
//位图属性结构
BITMAP Bitmap;
//位图文件头结构
BITMAPFILEHEADER bmfHdr;
//位图信息头结构
BITMAPINFOHEADER bi;
//指向位图信息头结构
LPBITMAPINFOHEADER lpbi;
//定义文件,分配内存句柄,调色板句柄
HANDLE fh, hDib, hPal,hOldPal=NULL;

//计算位图文件每个像素所占字节数
hDC = CreateDC("DISPLAY", NULL, NULL, NULL);
iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
DeleteDC(hDC);
if (iBits <= 1) wBitCount = 1;
else if (iBits <= 4) wBitCount = 4;
else if (iBits <= 8) wBitCount = 8;
else wBitCount = 24;

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;

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

//为位图内容分配内存
hDib = GlobalAlloc(GHND,dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER));
lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
*lpbi = bi;

// 处理调色板
hPal = GetStockObject(DEFAULT_PALETTE);
if (hPal)
{
hDC = ::GetDC(NULL);
hOldPal = ::SelectPalette(hDC, (HPALETTE)hPal, FALSE);
RealizePalette(hDC);
}

// 获取该调色板下新的像素值
GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER)
+dwPaletteSize, (BITMAPINFO *)lpbi, DIB_RGB_COLORS);

//恢复调色板
if (hOldPal)
{
::SelectPalette(hDC, (HPALETTE)hOldPal, TRUE);
RealizePalette(hDC);
::ReleaseDC(NULL, hDC);
}

//创建位图文件
fh = CreateFile(FileName.c_str(), GENERIC_WRITE,0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);


if (fh == INVALID_HANDLE_VALUE) return FALSE;

// 设置位图文件头
bmfHdr.bfType = 0x4D42; // "BM"
dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;
bmfHdr.bfSize = dwDIBSize;
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;
// 写入位图文件头
WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
// 写入位图文件其余内容
WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL);
//清除
GlobalUnlock(hDib);
GlobalFree(hDib);
CloseHandle(fh);

return TRUE;
}

void mGLRender()
{
glClearColor(0.9f,0.9f,0.3f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
gluPerspective(30.0, 1.0, 1.0, 10.0);
glMatrixMode(GL_MODELVIEW);
gluLookAt(0, 0, -5, 0, 0, 0, 0, 1, 0);
glBegin(GL_TRIANGLES);
glColor3d(1, 0, 0);
glVertex3d(0, 1, 0);
glColor3d(0, 1, 0);
glVertex3d(-1, -1, 0);
glColor3d(0, 0, 1);
glVertex3d(1, -1, 0);
glEnd();
glFlush(); // remember to flush GL output!
}

int _tmain(int argc, _TCHAR* argv[])
{
const int WIDTH = 500;
const int HEIGHT = 500;

// Create a memory DC compatible with the screen
HDC hdc = CreateCompatibleDC(0);
if (hdc == 0) cout<<"Could not create memory device context";

// Create a bitmap compatible with the DC
// must use CreateDIBSection(), and this means all pixel ops must be synchronised
// using calls to GdiFlush() (see CreateDIBSection() docs)
BITMAPINFO bmi = {
{ sizeof(BITMAPINFOHEADER), WIDTH, HEIGHT, 1, 32, BI_RGB, 0, 0, 0, 0, 0 },
{ 0 }
};
DWORD *pbits; // pointer to bitmap bits
HBITMAP hbm = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void **) &pbits,
0, 0);
if (hbm == 0) cout<<"Could not create bitmap";

//HDC hdcScreen = GetDC(0);
//HBITMAP hbm = CreateCompatibleBitmap(hdcScreen,WIDTH,HEIGHT);

// Select the bitmap into the DC
HGDIOBJ r = SelectObject(hdc, hbm);
if (r == 0) cout<<"Could not select bitmap into DC";

// Choose the pixel format
PIXELFORMATDEscriptOR pfd = {
sizeof (PIXELFORMATDEscriptOR), // struct size
1, // Version number
PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL, // use OpenGL drawing to BM
PFD_TYPE_RGBA, // RGBA pixel values
32, // color bits
0, 0, 0, // RGB bits shift sizes...
0, 0, 0, // Don't care about them
0, 0, // No alpha buffer info
0, 0, 0, 0, 0, // No accumulation buffer
32, // depth buffer bits
0, // No stencil buffer
0, // No auxiliary buffers
PFD_MAIN_PLANE, // Layer type
0, // Reserved (must be 0)
0, // No layer mask
0, // No visible mask
0 // No damage mask
};
int pfid = ChoosePixelFormat(hdc, &pfd);
if (pfid == 0) cout<<"Pixel format selection failed";

// Set the pixel format
// - must be done *after* the bitmap is selected into DC
BOOL b = SetPixelFormat(hdc, pfid, &pfd);
if (!b) cout<<"Pixel format set failed";

// Create the OpenGL resource context (RC) and make it current to the thread
HGLRC hglrc = wglCreateContext(hdc);
if (hglrc == 0) cout<<"OpenGL resource context creation failed";
wglMakeCurrent(hdc, hglrc);

// Draw using GL - remember to sync with GdiFlush()
GdiFlush();
mGLRender();

SaveBmp(hbm,"output.bmp");
/*
Examining the bitmap bits (pbits) at this point with a debugger will reveal
that the colored triangle has been drawn.
*/

// Clean up
wglDeleteContext(hglrc); // Delete RC
SelectObject(hdc, r); // Remove bitmap from DC
DeleteObject(hbm); // Delete bitmap
DeleteDC(hdc); // Delete DC

return 0;
}

好了,编译成功,运行,确实是可以啊!看看步骤是什么样的:

CreateCompatibleDC

创建dc

CreateDIBSection

创建图像

SelectObject

图像选入DC

SetPixelFormat

设置像元格式

wglCreateContext

创建RC

wglMakeCurrent

选择RC

mGLRender

开始渲染

SaveBmp

保存图像(这段是我从网上随便摘下来的)

...

清理

好的,既然C++可以,那么Python……

等等,Python好像不行!

单单是OpenGL的世界乱了,也就算了,偏偏Python也来凑热闹。PyWin32里我死活找不到 CreateDIBSection。好吧,PyWin32找不到,那么我还有PIL。里面有个ImageWin.Dib,我试过,不行。总是在 SetPixelFormat中出现问题。后来我把 CreateDIBSection的部分整个注释掉改成类似:

HDC hdcScreen = GetDC(0);
HBITMAP hbm = CreateCompatibleBitmap(hdcScreen,WIDTH,HEIGHT);

的代码。当然这是C++ 的改动,python改动也类似。因为这两个函数PyWin32里有,现在通过了。并且运行到了wglCreateContext的步骤。等等,提示空间 不够?什么空间不够?我在C++中都运行好好的。对比两个语言的两段代码,完全一样的步骤,居然一个可以一个就是不行!发个邮件给pyopengl的邮件 列表吧,几天没回应……真的晕了。

大概可能是我不懂怎么玩PyWin32或者PyOpenGL,或者PIL的Dib类我用得不对,但是我在泡了三天的google后,我放弃了。与其在这个问题上拖延时间,不如另辟蹊径。(如果你成功得在Python下离屏渲染了,一定要告诉我哦!)

既然C++可以,为什么不用C++来做?然后用Swig来绑定?不就是创建一个环境吗?我在C++中创建好,然后在Python中渲染,然后在C++中关闭环境。反正环境在哪里不是一样创建!

2. 来吧

现在我的思路就定下来,用C++写两个函数,用来创建离屏RC环境和关闭环境。名字就叫StartBmpContext和EndBmpContext。

创建一个工程。叫glBmpContext。然后做一些什么取消stdafx,清空等善前工作。然后写入内容。

#include
#include
#include
#include
#include
using namespace std;

static HDC hdc;
static HBITMAP hbm;
static HGDIOBJ r;
static HGLRC hglrc;
static DWORD *pbits;// pointer to bitmap bits

static int WIDTH = 120;
static int HEIGHT = 90;

__declspec(dllexport) void StartBmpContext(int width,int height)
{
WIDTH = width;
HEIGHT = height;

// Create a memory DC compatible with the screen
hdc = CreateCompatibleDC(0);
if (hdc == 0) cout<<"Could not create memory device context";

// Create a bitmap compatible with the DC
// must use CreateDIBSection(), and this means all pixel ops must be synchronised
// using calls to GdiFlush() (see CreateDIBSection() docs)
BITMAPINFO bmi = {
{ sizeof(BITMAPINFOHEADER), WIDTH, HEIGHT, 1, 32, BI_RGB, 0, 0, 0, 0, 0 },
{ 0 }
};

hbm = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void **) &pbits,
0, 0);
/*HBITMAP hbm = CreateCompatibleBitmap(hdc,WIDTH,HEIGHT);*/
if (hbm == 0) cout<<"Could not create bitmap";

// Select the bitmap into the DC
r = SelectObject(hdc, hbm);
if (r == 0) cout<<"Could not select bitmap into DC";

// Choose the pixel format
PIXELFORMATDEscriptOR pfd = {
sizeof (PIXELFORMATDEscriptOR), // struct size
1, // Version number
PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL, // use OpenGL drawing to BM
PFD_TYPE_RGBA, // RGBA pixel values
32, // color bits
0, 0, 0, // RGB bits shift sizes...
0, 0, 0, // Don't care about them
0, 0, // No alpha buffer info
0, 0, 0, 0, 0, // No accumulation buffer
32, // depth buffer bits
0, // No stencil buffer
0, // No auxiliary buffers
PFD_MAIN_PLANE, // Layer type
0, // Reserved (must be 0)
0, // No layer mask
0, // No visible mask
0 // No damage mask
};
int pfid = ChoosePixelFormat(hdc, &pfd);
cout< if (pfid == 0) cout<<"Pixel format selection failed";

// Set the pixel format
// - must be done *after* the bitmap is selected into DC
BOOL b = SetPixelFormat(hdc, pfid, &pfd);
if (!b) cout<<"Pixel format set failed";

// Create the OpenGL resource context (RC) and make it current to the thread
hglrc = wglCreateContext(hdc);
if (hglrc == 0) cout<<"OpenGL resource context creation failed";
wglMakeCurrent(hdc, hglrc);

}

int SaveBmp(HBITMAP hBitmap, char* FileName)
{
//...同上面

return 0;

}

__declspec(dllexport) int SaveBmp(char* FileName)
{
return SaveBmp(hbm,FileName);
}

__declspec(dllexport) int GetWidth()
{
return WIDTH;
}
__declspec(dllexport) int GetHeight()
{
return HEIGHT;
}

__declspec(dllexport) void GetMemBmpData(char **s,int *slen)
{
*s = (char*)pbits;
*slen = WIDTH*HEIGHT*4;
}

__declspec(dllexport) void EndBmpContext()
{
// Clean up
wglDeleteContext(hglrc); // Delete RC
SelectObject(hdc, r); // Remove bitmap from DC
DeleteObject(hbm); // Delete bitmap
DeleteDC(hdc); // Delete DC
}

其实这里做得事情也就是这样,把前面那段C++代码拆开,把开始渲染前和渲染结束后两个部分单独拆出来,放到Start和End两个函数里。为了能在最后做清理工作,把一些句柄做成全程静态变量。提到开头而已。

等一下,多了很多函数。

是的。这里多了SaveBmp,这个是为了测试数据的正确性。用vc的方法保存bmp图像。但是我并不想 在vc中保存图像。太麻烦了。我们有PIL啊!保存只要一句的PIL啊~~~~~所以我需要有个函数读取bmp图像的信息。所以我添加了个 GetMemBmpData函数。用于获取图像数据的二进制表示。当然,渲染图像大小不可以定死,所以我暴露了获取图像大小的函数,并在初始化环境的时候 用两个参数定义宽高。

好了,编译,链接,成功。(需要说明的是,这里的GetMemBmpData的参数很奇怪,这是因为要返回二进制时Swig的特殊要求决定的)

我们现在有了C++的库了。

好,开始定义glBmpContext.i,这是重点!

%module glBmpContext
%include "cstring.i"

%cstring_output_allocate_size(char **s, int *slen, free(*$1));


%{
#include
#include
#include
#include
#include
using namespace std;

void StartBmpContext(int w,int h);
int SaveBmp( char* FileName );
void GetMemBmpData(char **s,int *slen);
void EndBmpContext();
int GetWidth();
int GetHeight();
%}


void StartBmpContext(int w,int h);
int SaveBmp( char* FileName );
void GetMemBmpData(char **s,int *slen);
void EndBmpContext();
int GetWidth();
int GetHeight();

首先,我们定义模块名 称,然后引入一个叫cstring的swig预定义模块,以及定义一种返回值形式。引入这个模块是因为我们需要在GetMemBmpData中返回图像格 式的二进制形式给Python,然后通过PIL.Image的fromstring函数转化成图像并可以用save保存。

Python中不单单是int,double,这样的简单类型。一些如数组,指针,字典,等等就比较麻烦 了。Swig定义了很多预定义的模块来处理这些东西。通过%include 来定义这些数据格式和操作。这才是从C++到Python的恶梦。也是swig最核心的东西。这些东西是很多的,需要我们慢慢去掌握。

先掌握两个。一个是字符串。在Python中字符串是一个很强大的东西,但在swig定义中却看起来不是 那么强大。因为它被定义成c的字符串形式。一个 char* !不错,是char*。看SaveBmp的参数,是一个char*。这就是Python中的字符串!在Python中调用就像这样:

SaveBmp("f:/image/img.bmp")

好了,再看一个,返回一个二进制数据对象!这个比较复杂,可以看,这个解释十分详细。还有好几种类型。我们用的是最后那个。因为C++/C不比Python,可以返回一个列表,它只能返回一个东西。所以在Python绑定定义中要用参数来代替返回。

还有更多的东西可以看。

函数定义像这样:

void foo(char **s, int *sz) {
*s = (char *) malloc(64);
*sz = 64;
// Write some binary data
...
}

在swig定义.i 文件中就要这样写:

%cstring_output_allocate_size(char **s, int *slen, free(*$1));
...
void foo(char **s, int *slen);

在Python中就要这样调:

>>> foo()
'\xa9Y:\xf6\xd7\xe1\x87\xdbH;y\x97\x7f\xd3\x99\x14V\xec\x06\xea\xa2\x88'
>>>

呵呵,很奇妙吧!

我也是第一次看到这种做法!

其他应该都看得懂了。

好了,现在我们定义setup.py:

from distutils.core import setup,Extension
include_dirs = []
libraries = ['glBmpContextD','opengl32','glu32']
library_dirs = ['./glBmpContext/']
extra_link_args = []

glBmpContext_module = Extension('_glBmpContext',
sources = ['glBmpContext_wrap.cpp'],
include_dirs = include_dirs,
libraries = libraries,
library_dirs = library_dirs,
extra_link_args = extra_link_args,
)
setup(name='glBmpContext wrapper',
version='1.0',
py_modules=["glBmpContext"],
ext_modules=[glBmpContext_module],
)

这个和前一个例子很像。特别注意的是Libraries,这里放了opengl32 和glu32 是为了能链接通过。

好了,写个脚本来运行swig和编译。

@echo off
swig -c++ -python -modern -new_repr -I. -o glBmpContext_wrap.cpp glBmpContext.i
python.exe setup.py build
copy .\build\lib.win32-2.4\*.* .\bin\
pause

好了,运行编译通过后就可以了。这个脚本还把生成的pyd拷贝到bin目录下。

好了,在bin目录下建立一个测试脚本

转自:
阅读(1545) | 评论(0) | 转发(0) |
0

上一篇:用Swig让python使用C++库

下一篇:SWIG Tutorial

给主人留下些什么吧!~~