在Boost中,有一个图像处理库,那就是GIL,该库是由鼎鼎大名的Adobe公司贡献的,它在图像处理方面的优势可想而知。使用GIL,可以非常方便地对图像数据进行处理而不用考虑太多的细节,比如很简单地定位图像的行、列或任意像素,可以任意定位图像中的某一个颜色通道,甚至提取其中一个通道构建另外一个灰度图像,可以方便地将RGB图像转换成RGBA、灰度图像或者反过来转换,可以方便地旋转、倒置图像......优点实在是太多,我也说不好。当然,最重要的优点是它的通用性和性能。
但是使用Boost.GIL有一个很大的障碍,那就是它的IO扩展需要编译第三方的图像解码库做支持,而且这个编译过程很麻烦。作为一个跨平台的库,这么做无可厚非,但是在Windows平台上,就没有必要使用第三方的图像解码库了,难道Windows提供的图像解码功能还不够强吗?
在Visual C++中,有一个类CImage可以读取任意格式的图像文件,无论bmp、gif、tiff、jpeg还是png,都行。还可以将图像数据保存到文件,还可以非常方便地将图像显示到屏幕上。下面,我将使用MFC实现一个简单的SDI程序,在该程序的基础上,可以非常方便第添加功能,使用GIL对图像数据进行操作。
第一步,使用MFC向导创建一个SDI程序。
第二步,在文档类中添加几个变量,用来保存图像数据,image_src保存源图像数据,image_dst保存修改后的图像数据,同时定义两个view,用到了GIL中的类型,可以看到,我都是使用的RGBA模式的图像数据。代码如下:
CImage image_src;
rgba8_view_t view_src;
CImage image_dst;
rgba8_view_t view_dst;
第三步,重写文档类的OnOpenDocument()方法。在这个方法中,使用CImage类读取图像文件,然后使用GIL将图像文件的数据复制到image_src和image_dst中。不管原始图像是RGB还是RGBA的,最后统一都变成了RGBA的。代码如下:
BOOL CGIL_StudyDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;
CImage loader;
loader.Load(lpszPathName);
if(loader.IsNull())
return FALSE;
unsigned char* buffer_loader = (unsigned char*)loader.GetBits()+(loader.GetPitch()*(loader.GetHeight()-1));
if(!image_src.IsNull())image_src.Destroy();
image_src.Create(loader.GetWidth(),loader.GetHeight(),32);
unsigned char* buffer_src = (unsigned char*)image_src.GetBits()+(image_src.GetPitch()*(image_src.GetHeight()-1));
view_src = interleaved_view(image_src.GetWidth(),image_src.GetHeight(),(rgba8_pixel_t*)buffer_src,image_src.GetWidth()*4);
if(!image_dst.IsNull())image_dst.Destroy();
image_dst.Create(loader.GetWidth(),loader.GetHeight(),32);
unsigned char* buffer_dst = (unsigned char*)image_dst.GetBits()+(image_dst.GetPitch()*(image_dst.GetHeight()-1));
view_dst = interleaved_view(image_dst.GetWidth(),image_dst.GetHeight(),(rgba8_pixel_t*)buffer_dst,image_dst.GetWidth()*4);
switch(loader.GetBPP()){
case 24:
{
rgb8_view_t view_loader = interleaved_view(
loader.GetWidth(),loader.GetHeight(),
(rgb8_pixel_t*)buffer_loader,std::abs(loader.GetPitch()));
copy_pixels(
color_converted_view<rgba8_image_t::value_type>(view_loader),
view_src);
copy_pixels(
color_converted_view<rgba8_image_t::value_type>(view_loader),
view_dst);
break;
}
case 32:
{
rgba8_view_t view_loader = interleaved_view(
loader.GetWidth(),loader.GetHeight(),
(rgba8_pixel_t*)buffer_loader,loader.GetWidth()*4);
copy_pixels(
color_converted_view<rgba8_image_t::value_type>(view_loader),
view_src);
copy_pixels(
color_converted_view<rgba8_image_t::value_type>(view_loader),
view_dst);
break;
}
default:
assert(false);
break;
}
return TRUE;
}
第四步,重现MFC视图类中的OnDraw()方法,在窗口中显示image_dst,代码如下:
void CGIL_StudyView::OnDraw(CDC* pDC)
{
CGIL_StudyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
if(pDoc->image_dst.IsNull())return;
pDoc->image_dst.Draw(*pDC,0,0,pDoc->image_dst.GetWidth(),pDoc->image_dst.GetHeight());
}
至此,简单的框架搭建完成。如果要对图像数据进行操作,只需要操作view_src,然后将结果写入view_dst即可。比如,要使用GIL自带的例子中的x_gradient算法,只需要以下两个步骤:
1、将GIL例子中的几个模板函数代码复制到我们的项目中,如下:
template <typename SrcView, typename DstView>
void x_gradient(const SrcView& src, const DstView& dst) {
for (int y=0; y<src.height(); ++y) {
typename SrcView::x_iterator src_it = src.row_begin(y);
typename DstView::x_iterator dst_it = dst.row_begin(y);
for (int x=1; x<src.width()-1; ++x) {
for (int c=0; c<num_channels<SrcView>::value; ++c)
dst_it[x][c] = (src_it[x-1][c]- src_it[x+1][c])/2;
}
}
}
2、为我们的SDI程序添加一个菜单,并在菜单的处理函数中调用上面的函数,如下:
void CGIL_StudyView::OnXGradient()
{
CGIL_StudyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
x_gradient(pDoc->view_src,pDoc->view_dst);
Invalidate();
}
运行程序,点击这个菜单,就可以看到效果了。