DevIL快速入门
DevIL 全名是 “Developer’s Image Library”,它是一个多功能的图像库,能过很方便地载入、修改和保存图片。
其原名是 OpenIL,后来是因为 SGI 的要求才改名为 DevIL1。
本文主要介绍 DevIL 载入和保存图片的功能。DevIL 虽然有处理图片的功能,但并不够强,不如使用 Boost.GIL,
因此不介绍 DevIL 处理图片的功能。
特点
DevIL 具有几个非常好的特性:
- 支持包括 PNG、JPG、TGA 等 30 多种图片格式。
- 与 OpenGL 类似的 API 风格。
- 十分轻巧
- 有许多辅助函数(ilut)
安装
理论上安装 DevIL 不会很艰难。对于 Ubuntu 用户而言,可以直接使用下列命令来安装:
1 | sudo apt-get install libdevil-dev |
具体的下载、编译、安装的页面在此。
初始化
接下来正式介绍如何使用 DevIL。首先,我们需要添加头文件:
1 | #include <IL/il.h> |
一般情况下,动态链接的 DevIL 会自动加载,不必调用特定的函数。但在某些情况下可能不会。因此,为了兼容性,我们最好手动载入:
1 2 | // 在程序开始的地方 ilInit(); |
仅仅一行代码,并没有什么关系。
加载图片
DevIL 中使用了和 OpenGL 一样的对象创建方式。为了加载图片,我们首先要创建一个图片对象:
1 2 3 | ILuint image = 0; ilGenImages(1, &image); assert(image != 0); // 检查是否创建成功 |
也许你会发现第二行非常熟悉,确实和 OpenGL 中创建缓冲区的调用长得非常像,因此很多使用 OpenGL 的开发者能很快上手。
当然,你如果觉得这还麻烦了,可以只创建一个:
1 | ILuint image = ilGenImage(); |
就像 OpenGL 一样,需要绑定当前的图片对象。绑定后,所有的操作都是在此图片对象上的,除非解绑:
1 2 3 4 5 | ilBindImage(image); // ... ilBingImage(0); // 解绑图片对象 |
绑定好图片对象后,就可以直接使用 ilLoad
函数来加载图片了:
1 | ilLoad(IL_PNG, "sample.png"); |
上面的 IL_PNG
是指定图片格式为PNG。
当然,可以使用更简单的 ilLoadImage
:
1 | ilLoadImage("sample.png"); |
使用 ilLoadImage
就不需要手动指定图片格式了,DevIL 会自动检测。
我们可以使用 ilGetError
来查看错误。如果加载过程中没有出错,ilGetError
会返回 IL_NO_ERROR
。
1 | assert(ilGetError() == IL_NO_ERROR); |
注意
ilDeleteImage
删除: ilDeleteImage(image); // 删除单个图片对象 ilDeleteImages(1, &image); // 当然也可以批量删除
创建材质
加载完图片后,还只是将数据托管在 DevIL 内部。为了能够将数据提供给 OpenGL 或 DirectX 来创建材质,我们使用 ilGetData()
。
同时,我们还需获取图片的相关的信息,如宽度、高度、图片存储格式等等,这些都可以使用 ilGetInteger
来获取。
下面是在 OpenGL 中创建材质的过程:
1 2 3 4 5 6 7 8 9 | ilBindImage(image); // 绑定当前图片对象 GLint width = ilGetInteger(IL_IMAGE_WIDTH); // 获取图片宽度 GLint height = ilGetInteger(IL_IMAGE_HEIGHT); // 获取图片高度 GLenum format = ilGetInteger(IL_IMAGE_FORMAT); // 获取图片像素格式 ILubyte *ptr = ilGetData(); // 获取图片数据的指针 ilBindImage(0); // 创建材质 glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, ptr); |
提示
IL_IMAGE_DEPTH
)、 图片数据的大小(IL_IMAGE_SIZE_OF_DATA
)、BPP(”Bytes Per Pixel“,每个像素所占字节数,IL_IMAGE_BPP
或 IL_IMAGE_BYTES_PER_PIXEL
)、 Bit Per Pixel(每个像素所占位数,IL_IMAGE_BITS_PER_PIXEL
)、图片格式(IL_IMAGE_TYPE
)、 水平/竖直平移量(IL_IMAGE_OFFX
和IL_IMAGE_OFFY
)、图片原点(IL_IMAGE_ORIGIN
)、 颜色通道数(IL_IMAGE_CHANNELS
)等。 提示
对于 DirectX,需要手写
switch
来切换。 保存图片
DevIL 中保存图片很简单,只需要先绑定图片对象,指定保存路径就可直接保存。DevIL 会自动通过文件后缀名来确定图片格式。
1 2 3 | ilBindImage(image); ilSaveImage("output.png"); ilBindImage(0); |
可是我们并没有对图片做什么处理啊,保存它有什么用?确实,我们不会去用 DevIL 来做什么特效。但有一个场景却很常用,就是保存截图。
对于 OpenGL,DevIL 的工具库 ilut
已经帮我们做到了这一点。我们可以非常简单的写出保存截图的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #define ILUT_USE_OPENGL // 通知ilut使用OpenGL #include <IL/ilut.h> // 保存截图 bool TakeScreenshot(const std::string &filepath) { ILuint image = ilGenImage(); ilBindImage(image); ilutGLScreen(); // 将当前OpenGL的颜色缓冲区的数据复制到image中 ilSaveImage(filepath.data()); // 保存图片 ilBindimage(0); ilDeleteImage(image); // 记得释放图片对象 return ilGetError() == IL_NO_ERROR; } |
没错,ilutGLScreen
帮我们做了一切。只是非常可惜,截图的函数是 OpenGL 专属的。
但它是怎么工作的呢?我在 GitHub 上找到其源码,其过程非常简短。
下面是 GitHub 上的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //! Takes a screenshot of the current OpenGL window. ILboolean ILAPIENTRY ilutGLScreen() { ILuint ViewPort[4]; ilutCurImage = ilGetCurImage(); if (ilutCurImage == NULL) { ilSetError(ILUT_ILLEGAL_OPERATION); return IL_FALSE; } glGetIntegerv(GL_VIEWPORT, (GLint*)ViewPort); if (!ilTexImage(ViewPort[2], ViewPort[3], 1, 3, IL_RGB, IL_UNSIGNED_BYTE, NULL)) return IL_FALSE; // Error already set. ilutCurImage->Origin = IL_ORIGIN_LOWER_LEFT; glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0, 0, ViewPort[2], ViewPort[3], GL_RGB, GL_UNSIGNED_BYTE, ilutCurImage->Data); return IL_TRUE; } |
我们来简要分析下这个过程在干什么。
首先是获取当前绑定的图片对象:
1 2 3 4 5 | ilutCurImage = ilGetCurImage(); if (ilutCurImage == NULL) { // 检查并报错 ilSetError(ILUT_ILLEGAL_OPERATION); return IL_FALSE; } |
然后获取当前 OpenGL 视图的信息:
1 2 3 4 5 | ILuint ViewPort[4]; // ... glGetIntegerv(GL_VIEWPORT, (GLint*)ViewPort); |
此时,ViewPort
中存储的分别是原点的 X 坐标和 Y 坐标,以及视图的宽度和高度。
根据获取来的视图信息,就需要调整图片的参数:
1 2 3 4 | // ilTexImage分别设置的是图片的宽度、高度、BPP、颜色通道数量、像素格式和数据格式 // 最后一个参数是图片数据的指针,但是图片数据要在之后读取,因此这里填nullptr if (!ilTexImage(ViewPort[2], ViewPort[3], 1, 3, IL_RGB, IL_UNSIGNED_BYTE, NULL)) return IL_FALSE; // 设置失败 |
然后是从 OpenGL 的颜色缓冲中读取数组:
1 2 3 4 5 6 7 8 | // 因为OpenGL的原点是在左下角,而屏幕坐标的原点却在左上角,因此此处做点调整 ilutCurImage->Origin = IL_ORIGIN_LOWER_LEFT; // 更改数据的内存对齐,避免读出来的数据格式不对 glPixelStorei(GL_PACK_ALIGNMENT, 1); // 读出数据 glReadPixels(0, 0, ViewPort[2], ViewPort[3], GL_RGB, GL_UNSIGNED_BYTE, ilutCurImage->Data); |
那么此时截图的方法已经出来了。对于 DirectX,只需先创建图片对象并重设其参数,
然后读出颜色数据到图片中,最后保存即可。不过我不怎么熟悉 DirectX,因此这里并没有给出 DirectX 的截图代码了。
结尾
从上面的文章我们已经了解了 DevIL 库,并且能够运用到实际工程中了。
事实上类似的库还有很多,例如 SOIL 和 FreeImage。
如果你只是专注于一种格式,也许像 libpng 和 libjpg 更适合你。
因此在实际中,需要我们酌情选择合适的库来提高自己的开发效率。