要给项目中增加一个新的模块,需要先在服务器端做一些图片处理相关的工作。本来,对图片
做一些诸如ALPHA混合旋转缩放的操作,在游戏客户端应该是很容易的事。但是这事要在服务
器做,就不得不引入一些第三方库。反正我们的服务器运行于WINDOWS下,这里又需要处理
JPG图片的加载,我就考虑到了GDI+。
在这之前对GDI+没有过任何接触。直接翻了MSDN,还好居然有个一系列的usage。GDI+的Image
本身支持JPG的直接载入。但是并没有我理想中的CreateFromMemory( const void *buf )接口。
看起来唯一可以从内存创建Gdiplus::Image对象的方法是从一个叫IStream*的COM东西。我揣摩
微软为什么没有提供我理想中的那个接口,或者说要把GDI+设计成这样,可能还是考虑到对多语
言的支持。于是问题转换为如何将一个C语言的const void*转换为IStream*。我甚至在开始的时候
感觉到是不是要自己实现个Stream。后来在google上找到了一个似乎是标准的方法:首先创建个
HGLOBAL对象,然后通过GlobalLock就可以将一个C的const void*直接memcpy到这个HGLOBAL
里,最后,通过CreateStreamOnHGlobal这样的接口就可以得到一个IStream。
恶心的是,基于之前对服务器内存使用的优化,我现在对于内存的使用非常敏感(谁说现在内存
大了就可以任意malloc了??)。上面那个过程对于资源的管理在MSDN文档中似乎显得有点
模糊。CreateStreamOnHGlobal函数的第二个参数指定当IStream->Release的时候,是否会自动
删除这个HGLOBAL对象。我虽然对COM不懂,但也知道它的对象是基于一种引用计数的管理方式。
逐字看了下文档,发现一个final单词,原来是IStream->Release最后一次释放时,会同时释放掉
这个HGLOBAL对象。更让人发指的是,我猜测Image( IStream * )来创建Image时,Image又
会对这个IStream进行一次AddRef。我发觉MSDN对于Gdiplus::Image::FromStream函数的说明
也有点模糊。我揣摩使用FromStream获得的Image*,是否需要手动去delete?这个地方的内存
资源管理,一定得搞个水落石出。结果是,FromStream的实现就是简单地new了个Image。而
Image内部肯定会对IStream进行AddRef,并且,如果在Image销毁前销毁这个HGLOBAL,这个
Image基本也就废了。
也就是说,Image本身不对HGLOBAL中的图片数据进行复制。囧。别想让我再写个wrap class把
HGLOBAL和Image纠结在一起,简单考虑,将CreateStreamOnHGlobal第二个参数设为TRUE。
要将一个Image保存为一段内存,也比较麻烦。我的方法和google上的相同。当然,微软的库依
然让我在很多细节上栽跟斗(如前所说,可能这是基于多语言支持的考虑)。首先需要创建个空
的IStream,即CreateStreamOnHGlobal第一个参数为NULL。然后将Image Save到这个IStream。
再根据该IStream::Seek获取其大小,自己再分配段内存,最后IStream::Read读取进来。同样,
需要注意相关内存资源的管理。
下午简单把以上两个过程简单封装了下。
下载代码。