logo资料库

利用C++类实现PNG图像读写及显示.pdf

第1页 / 共10页
第2页 / 共10页
第3页 / 共10页
第4页 / 共10页
第5页 / 共10页
第6页 / 共10页
第7页 / 共10页
第8页 / 共10页
资料共10页,剩余部分请下载后查看
利用 C++类实现 PNG 图像读写及显示(一) 摘 要 运用 libpng 库函数,设计了一个可读写 PNG 格式图像的 C++类。同时,在 VC++6.0 开 发平台下,设计出一个基于多文档结构的图像浏览器,实现 PNG 格式图像的读写及显 示。 关键词 PNG, 图像格式,C++,类 一、前言 PNG 是一种可携式网络图像格式(Portable Network Graphic Format,PNG),其名称来源于非 官方的“PNG's Not GIF”,是一种位图文件(Bitmap file)存储格式,读成“ping”[1]。在 20 世 纪 90 年代, GIF 已经普遍使用,然而在 1995 年新年前后,Unisys 和 CompuServe 两家公司突然 宣布编程使用 GIF 均需要向其支付版税,理由是他们拥有 LZW 压缩算法(GIF 格式采用该算法)的 版权。付费问题,迫使众多开发人员设计和开发新的用以代替 GIF 的图像格式。PNG 出现的初衷就 是为了替代 GIF 文件格式,避免支付版税。因此,设计 PNG 时,保留了 GIF 的众多特性,如(1) 使用彩色查找表(也称调色板)可支持 256 种颜色的彩色图像;(2)流式读/写性能(Streamability); (3)逐次逼近显示(Progressive display);(4)透明性(Transparency);(5)使用无损压缩等。 同时,也增加了一些 GIF 文件格式所不具备的特性,如(1)每个像素为 48 位的真彩色图像;(2) 每个像素为 16 位的灰度图像;(3)可为灰度图和真彩色图添加α通道;(4)添加图像的γ信息; (5)使用循环冗余码(Cyclic redundancy code,CRC)检测损害的文件;(6)加快图像显示的逐 次逼近显示方式;(7)标准的读/写工具包等。目前,PNG 已成为国际互网络联盟(World Wide Web Consortium ,W3C)推荐的标准[2],是互联网中常用的图像格式之一,常见的绘图软件和浏览器均 支持 PNG 图像浏览(其中 IE 4.0 以上版本均支持 PNG)。本文利用 http://www.libpng.org 提供 的 libpng 库[3],设计一个可读写 PNG 图像的 C++类,同时利用 VC++6.0 开发平台,通过 PNG 图像 浏览器的实例设计,说明 PNG 类的使用方法。 二、PNG 文件结构 PNG 图像由一个 8 字节的 PNG 文件署名(PNG file signature)域和按照特定结构组织的 3 个以 上的数据块(Chunk)组成。PNG 文件署名域是用来识别该文件是不是 PNG 文件,如果用十进制数表 示,该域的值依此是 137, 80, 78, 71, 13, 10, 26, 10,(对应的十六进制数为 89,50,4e, 47,0d, 0a,1a,0a)。PNG 数据块由表 1 所示的 4 个域构成[2]。根据类型的不同,PNG 数据块 又可分为两种:一种是关键数据块(Critical chunk),这是标准的数据块,另一种叫做辅助数据块 (Ancillary chunks),是可选的数据块。其中,关键数据块又定义了 4 个标准数据块,分别为(1) 文件头数据块 IHDR(Header chunk);(2)调色板数据块 PLTE(Palette chunk);(3)图像数据块 IDAT(Image data chunk);(4)图像结束数据 IEND(Image trailer chunk)。由于篇幅限制,这 里只介绍与编程设计最为密切的文件头数据块结构,具体见表 2,其他数据块格式可参考文献[2]。 名称 字节数 说明 表 1 PNG 数据块的结构 Length(长度) Chunk Type Code(数据块类型 码) 4 4 指定数据块中数据域的长度,其长度不超 过(231-1)字节 数据块类型码由 ASCII 字母(A-Z 和 a-z)组 成 Chunk Data(数据块数据) 可变长 存储按照 Chunk Type Code 指定的数据
CRC(循环冗余检测) 度 4 存储用来检测是否有错误的循环冗余码 表 2 文件头数据块的结构 名称 字节数 说明 Width (宽度) Height (长度) Bit depth (图像深度) 4 4 1 Color type (颜色类型) 1 Compression method (压 缩方法) Filter method (滤波器 方法) Interlace method (隔行 扫描方法) 1 1 1 图像宽度(单位:像素,0 为无效值) 图像高度(单位:像素,0 为无效值) 索引彩色图像:1,2,4 或 8 灰度图像:1,2,4,8 或 16 真彩色图像:8 或 16 0:灰度图像, 2:真彩色图像 3:索引彩色图像 4:带α通道数据的灰度图像 6:带α通道数据的真彩色图像 显示所用压缩方法,国际标准中只定义了一种方 法(method 0) 滤波器方法 0:非隔行扫描;1: Adam7(由 Adam M. Costello 开发的 7 遍隔行扫描方法 三、PNG 类设计 这一小节我们设计一个名为 MyPNG 的类,主要功能包括(1)将一幅 PNG 图像文件读入 内存,对应函数为 PngLoadImage (const char *pstrFileName);(2) 将内存中的图 像数据保存为 PNG 图像,对应函数为 PngSaveImage (const char *pstrFileName);(3) 显示 PNG 图像,对应函数为 Draw( CDC *pDC, int nX = 0, int nY = 0, int nWidth = -1, int nHeight = -1 )。通常,Window 应用程序中的其他格式的图像显示可通过显示其对 应位图实现。由于 PNG 图像数据的存放格式与位图格式不同,因此为了显示 PNG 图像,需 要将 PNG 图像数据转换成对应的位图格式数据,类中的成员函数 PngToBitmap()就是实现 这个功能。此外,显示位图像素数据时,需要知道位图文件信息,因此要将 PNG 图像的信 息(如宽度、高度等)转换成相应位图文件信息,实现该功能的类成员函数为 FillBitmapInfo()。于是,可得到 MyPNG 类的头文件定义: #ifndef _PNG_INC_ #define _PNG_INC_ #include "png.h" //声明 libpng 库函数、png 相关的结构体等信息的头文件 class MyPNG { public: MyPNG(); ~MyPNG(); bool PngToBitmap(); //将 Png 数据区转换到 bitmap 数据区 bool FillBitmapInfo(); BOOL Draw( CDC *pDC, int nX = 0, int nY = 0, int nWidth = -1, int nHeight = -1 ); //显示位图
BOOL PngLoadImage (const char *pstrFileName); //载入 PNG BOOL PngSaveImage (const char *pstrFileName); //将 pPixelBuffer 的数据保 存 PNG public: png_structp png_ptr; //libpng 定义的结构体指针,存放用于读/写 png 图像 的信息 png_infop info_ptr; // libpng 定义的结构体指针,存放 png 文件信息 png_byte *pbImage; //存放次序是 R-G-B //PNG 图像数据,该数据存放规则与位图相反(第一个像素在左下角,自下而上,从左 到右),即第一个像素在左上角(自上而下,从左到右存放) int cxImgSize, cyImgSize; //图像宽度、高度 int cImgChannels; //颜色通道 //图像深度,索引彩色图像:1,2,4 或 8;灰度图像:1,2,4,8 或 16;真彩色 图像:8 或 16 int iBitDepth; int iColorType; //颜色类型,0 --灰度图像,2--真彩色图像,3--索引彩色 图像,4--带α通道数据的灰度图像 //6--带α通道数据的真彩色图像 png_color bkgColor; //背景颜色 BITMAPINFO m_bmi; //位图文件信息 BYTE *pPixelBuffer; //存放 PNG 的位图数据缓存区 WORD wImgRowBytes; //每行的字节数 }; #endif 下面是类成员函数的详细定义。类的构造函数用来初始化成员变量信息,析构函数的 作用是释放内存空间。 bkgColor.red=127; bkgColor.green=127; bkgColor.blue=127; pPixelBuffer=NULL; MyPNG::MyPNG() { png_ptr=NULL; info_ptr=NULL; pbImage=NULL; cxImgSize=0; cyImgSize=0; wImgRowBytes=0; } MyPNG::~MyPNG() { if (pbImage!=NULL) free(pbImage); if (png_ptr!=NULL) free(png_ptr); if (info_ptr!=NULL) free(info_ptr); if (pPixelBuffer!=NULL) delete pPixelBuffer; }
利用 C++类实现 PNG 图像读写及显示(二) 成员函数 PngLoadImage 从路径 pstrFileName 读入 PNG 图像,并将 PNG 图像数据转换 成位图数据,设置位图信息。如果执行函数成功,返回 TRUE,失败则返回 FALSE。函数中 使用了一个例外处理的宏定义,Try{} Catch(){};其详细定义在另外一个头文件 cexcept.h (libpng 例程 visupng 有该文件定义),这里不再列举。 BOOL MyPNG::PngLoadImage (const char *pstrFileName) { FILE *pfFile; png_byte pbSig[8]; double dGamma; int i; png_color_16 *pBackground; png_uint_32 ulChannels, ulRowBytes; static png_byte **ppbRowPointers = NULL; if (!pstrFileName) {pbImage=NULL; return FALSE; } //文件名指针为空则 返回 if (!(pfFile = fopen(pstrFileName, "rb"))) { pbImage=NULL; return FALSE; } //打开 PNG 文件 fread(pbSig, 1, 8, pfFile); //读取 8 位 PNG 文件署名 if (!png_check_sig(pbSig, 8)) { pbImage=NULL; return FALSE; } //检查 PNG 文件署名 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, (png_error_ptr)png_cexcept_error, (png_error_ptr)NULL); //创建结构 体信息 if (!png_ptr) {pbImage=NULL; return FALSE;} //创建失败,返回 info_ptr = png_create_info_struct(png_ptr); //创建结构体 //创建失败,释放已创建信息,返回 false if (!info_ptr) { png_destroy_read_struct(&png_ptr, NULL, NULL); pbImage=NULL; return FALSE; } Try { #if !defined(PNG_NO_STDIO) png_init_io(png_ptr, pfFile); //初始化 png 结构体 #else png_set_read_fn(png_ptr, (png_voidp)pfFile, png_read_data); #endif png_set_sig_bytes(png_ptr, 8); //设置 PNG 文件署名 png_read_info(png_ptr, info_ptr); //读取 PNG 信息 png_get_IHDR(png_ptr, info_ptr, (unsigned long *) &cxImgSize, (unsigned long *)&cyImgSize, &iBitDepth, &iColorType, NULL, NULL, NULL); //获取 宽度、高度、颜色深度、颜色类型等信息 //将图像数据扩展成每个像素由 3 个 8 比特组成 if (iBitDepth == 16) png_set_strip_16(png_ptr); if (iColorType == PNG_COLOR_TYPE_PALETTE) png_set_expand(png_ptr); if (iBitDepth < 8) png_set_expand(png_ptr); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_expand(png_ptr); if (iColorType == PNG_COLOR_TYPE_GRAY || iColorType == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); if (png_get_bKGD(png_ptr, info_ptr, &pBackground)) //设置背景透明颜 色,
{ png_set_background(png_ptr, pBackground, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); bkgColor.red = (byte) pBackground->red; bkgColor.green = (byte) pBackground->green; bkgColor.blue = (byte) pBackground->blue; } else {bkgColor.red=0;bkgColor.green=0; bkgColor.blue =0; } //判断是否需要 gamma 转换 if (png_get_gAMA(png_ptr, info_ptr, &dGamma)) png_set_gamma(png_ptr, (double) 2.2, dGamma); png_read_update_info(png_ptr, info_ptr); //变换后,需要更新 info_ptr png_get_IHDR(png_ptr, info_ptr,(unsigned long *) &cxImgSize, (unsigned long *)&cyImgSize, &iBitDepth, &iColorType, NULL, NULL, NULL); //获 取宽度、高度、颜色类型、颜色深度等信息 ulRowBytes = png_get_rowbytes(png_ptr, info_ptr); //行字节数 ulChannels = png_get_channels(png_ptr, info_ptr); //通道信息 cImgChannels = ulChannels; if (pbImage!=NULL) { free(pbImage); pbImage=NULL; }//空间已经 存在,则先释放空间 if ((pbImage = (png_byte *) malloc(ulRowBytes * cyImgSize * sizeof(png_byte))) == NULL) { png_error(png_ptr, "Load PNG: Out of memory"); } if ((ppbRowPointers = (png_bytepp) malloc(cyImgSize * sizeof(png_bytep))) == NULL) //分配内存空间 { png_error(png_ptr, "Load PNG: Out of memory"); } for (i = 0; i < cyImgSize; i++) ppbRowPointers[i] = pbImage + i * ulRowBytes; // 设置 PNG 图像的每行地址 png_read_image(png_ptr, ppbRowPointers); //一次读入图像信息 png_read_end(png_ptr, NULL); //读取辅助块(additional chunks)信息 free (ppbRowPointers); //释放内存空间 ppbRowPointers = NULL; } Catch (msg) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if(ppbRowPointers) free (ppbRowPointers); fclose(pfFile); return FALSE; } fclose (pfFile); pbImage=NULL; FillBitmapInfo(); bool ret=PngToBitmap(); //将 Png 图像数据转换成位图数据 //填充 Bitmap 信息 return ret; } PngSaveImage 实现将内存图像数据保存为文件名为 pstrFileName 的 PNG 图像,如果 执行成功,函数返回 TRUE,失败则返回 FALSE。 BOOL MyPNG::PngSaveImage (const char * pstrFileName) { const int ciBitDepth = 8, ciChannels = 3; FILE *pfFile; png_uint_32 ulRowBytes; static png_byte **ppbRowPointers = NULL;
int i, iWidth,iHeight; iWidth=cxImgSize; iHeight=cyImgSize; if (!pstrFileName) return FALSE; // 文件指针为空则返回 if (!(pfFile = fopen(pstrFileName, "wb"))) return FALSE;//创建文件 // 创建标准的 PNG 结构信息 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, (png_error_ptr)png_cexcept_error, (png_error_ptr)NULL); if (!png_ptr) { fclose(pfFile); return FALSE; } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { fclose(pfFile); png_destroy_write_struct(&png_ptr, (png_infopp) NULL); return FALSE; } Try { #if !defined(PNG_NO_STDIO) png_init_io(png_ptr, pfFile); //初始化 png 结构体信息 #else png_set_write_fn(png_ptr, (png_voidp)pfFile, png_write_data, png_flush); #endif png_set_IHDR(png_ptr, info_ptr, iWidth, iHeight, ciBitDepth, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); //写入每像素 3 比特的 PNG 图像 png_write_info(png_ptr, info_ptr); //写入文件头信息 png_set_bgr(png_ptr); //将 BGR 次序转换为 RGB 次序 ulRowBytes = iWidth * ciChannels; // 每行的字节数 if ((ppbRowPointers = (png_bytepp) malloc(iHeight * sizeof(png_bytep))) == NULL) Throw "MyPNG: Out of memory"; //分配内存失败; //设置每行的指针地址 for (i = 0; i < iHeight; i++) ppbRowPointers[i] = pPixelBuffer + (iHeight-1-i) * (((ulRowBytes + 3) >> 2) << 2); png_write_image (png_ptr, ppbRowPointers);// 一次写入整幅图像 png_write_end(png_ptr, info_ptr); // 写辅助块信息 free (ppbRowPointers); //释放空间 ppbRowPointers = NULL; png_destroy_write_struct(&png_ptr, (png_infopp) NULL); //写操作结 束,释放空间 } Catch (msg) { png_destroy_write_struct(&png_ptr, (png_infopp) NULL); if(ppbRowPointers) free (ppbRowPointers); fclose(pfFile); return FALSE; } fclose (pfFile); return TRUE;}
利用 C++类实现 PNG 图像读写及显示(三) 函数 Draw 主要用来显示 PNG 图像对应的位图图像,通过指定宽度和高度,可实现图像 的缩放显示。该函数主要为 VC++应用程序编写。 BOOL MyPNG::Draw(CDC *pDC, int nX, int nY, int nWidth, int nHeight) { if( nWidth == -1 ) nWidth = cxImgSize; if( nHeight == -1 ) nHeight= cyImgSize; StretchDIBits( pDC->m_hDC, nX, nY,nWidth, nHeight,0, 0,cxImgSize, cyImgSize,pPixelBuffer,&m_bmi, BI_RGB, SRCCOPY ); return true; } 成员函数 FillBitmapInfo 的功能是根据 PNG 图像大小设置位图信息,为显示图像做准 备。 m_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); m_bmi.bmiHeader.biHeight=cyImgSize; bool MyPNG::FillBitmapInfo() //给变量 m_bmi(位图信息头)赋值 { m_bmi.bmiHeader.biWidth=cxImgSize; } 成员函数 PngToBitmap 主要实现将 PNG 图像数据转换成位图图像数据。 bool MyPNG::PngToBitmap() { m_bmi.bmiHeader.biPlanes = 1; m_bmi.bmiHeader.biBitCount = 24; m_bmi.bmiHeader.biCompression = 0; return true; BYTE *src, *dst; BYTE r, g, b, a; const int cDIChannels = 3; WORD wDIRowBytes; int xImg, yImg; if (pPixelBuffer!=NULL) { delete pPixelBuffer; pPixelBuffer=NULL; }// 空间已分配则先释放 wImgRowBytes = cImgChannels * cxImgSize;//每行的字节数 wDIRowBytes = (WORD) ((cDIChannels * cxImgSize + 3L) >> 2) << 2; pPixelBuffer=new BYTE[wDIRowBytes*cyImgSize]; //分配内存空间 if (pPixelBuffer==NULL) { AfxMessageBox("PNG to Bitmap:Out of memory."); return false;} //分配失败则返回 for (yImg = 0; yImg < cyImgSize; yImg++) // 拷贝图像数据 { src = pbImage + yImg * wImgRowBytes; dst = pPixelBuffer + (cyImgSize - yImg - 1) * wDIRowBytes ;//+ cxImgPos * cDIChannels; 为 RGB } for (xImg = 0; xImg < cxImgSize; xImg++) { r = *src++; g = *src++; b = *src++; *dst++ = b; *dst++ = g; *dst++ = r; //位图的次序为 BGR,PNG 的次序 if (cImgChannels == 4) { a = *src++; } //通道信息跳过 } return true; }
利用 C++类实现 PNG 图像读写及显示(四) 四、实例设计 在设计实例前,先准备好相关的库。PNG 文件的读写库为 libpng,由于 PNG 用到 LZ77 派生压缩算法,因此,编译读写库时,libpng 需要连接 zlib 库[4],这两个库的源代码可 在文献[3]和文献[4]中获得。下面开始讲解如何应用 VC++6.0 设计 PNG 图像浏览器。 第一步:建立一个名为 PngImage 的多文档框架应用程序,其 View 类选择继承于 CScrollView。 第二步:在应用程序 CPngImageApp 的初始化函数 InitInstance()中,加入以下代码 cmdInfo.m_nShellCommand=CCommandLineInfo::FileNothing; 该代码位于 ParseCommandLine(cmdInfo)后,目的是使程序启动时,不打开新的空白 文档。同时将字符串 IDR_PNGIMATYPE 修改为 “\nPNG\nPNG\nPNG(*.png)\n.png\nPNG.Document\nPNG Document”,目的是打开文件时, 默认的文件对话框只显示后缀名为 PNG 文件。 第三步:从 libpng 库和 zlib 库中,选择以下文件,将他们添加到 PngImage 项目中。 pngconf.h, png.h, libpng.lib, cexcept.h, zlib.lib, zlib.h, zconf.h 同时,将第三节设计的 MyPNG 类也添加到项目中。 第四步:为文档类 CPngImageDoc 添加公有成员变量 MyPNG m_png, 并改写 OnOpenDocument 函数,具体如下: BOOL CPngImageDoc::OnOpenDocument(LPCTSTR lpszPathName) { if (!CDocument::OnOpenDocument(lpszPathName)) return FALSE; BOOL ret=m_png.PngLoadImage((const char *)lpszPathName); return ret; } 重写文档类的虚函数 OnSaveDocument,具体如下: BOOL CPngImageDoc::OnSaveDocument(LPCTSTR lpszPathName) { BOOL ret=m_png.PngSaveImage((const char *)lpszPathName); if (ret==TRUE) else AfxMessageBox("保存失败!"); AfxMessageBox("保存成功!"); return ret; } 同时,为菜单 IDR_PNGIMATYPE 中的“另存为”添加消息映射,具体代码如下: void CPngImageDoc::OnFileSaveAs() { BOOL ret; static char BASED_CODE szSaveFilter[]="PNG(*.png)|*.png||"; //过滤文 件 //创建文件保存对话框 CFileDialog FileDlg(FALSE,"*.png",NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,szSaveFilter); if (FileDlg.DoModal()==IDOK) //如果选择确定按钮,则保存 { ret=m_png.PngSaveImage((const char *)FileDlg.m_ofn.lpstrFile); } } if (ret==TRUE) AfxMessageBox("另存成功!"); else AfxMessageBox("另存失败!");
分享到:
收藏