C++图像加载之libpng、FreeImage、stb_image详解
作者:略游
libpng、FreeImage、stb_image都是图像解析的开源库,这篇文章主要为大家详细介绍了这三者的使用方法,文中的示例代码讲解详细,需要的可以参考一下
一、前言
libpng、FreeImage、stb_image都是图像解析的开源库,由于三者我都简单使用过,于是做个总结对比。
二、对比
库 | 开源协议 | 编译依赖 | win编译难度 | 使用难度 | 格式支持 |
libpng | zlib | zlib | 自带vs工程 | 中 | 只支持png |
FreeImage | 混合 | 自带了7、8个库 | 有dll发行版 | 简单 | 很多 |
stb_image | MIT | 无 | 只有头文件 | 简单 | 常用的几个 |
它们的官网如下:
GitHub - nothings/stb: stb single-file public domain libraries for C/C++
总的来说,它们的开源协议都问题不大。源码编译stb_image最简单,因为它只有一个头文件。而libpng是操作png文件的库,代码比较复杂,但是它的优点是灵活,速度更快。FreeImage集成了各种加载库,支持的格式比较多。
接下来,我就按使用难度给出它们的基本代码。注意均是读取到uint32_t缓冲区,代表RGBA32位颜色,如下:
class Image { //other uint32_t* _data; array<unsigned, 2> _size; }
注意我并没有处理字节序问题,是写死的,在不同大小端的系统运行,应该改进一下代码(我懒得很,要等到遇到问题再解决,就是交换一下单个像素RGBA通道的读取顺序)。
觉得有用,请点赞、收藏、关注。我写这篇文件就是因为有人get了我以前写的libpng的文章。
三、stb_image
读取如下,确实很简单,直接返回的就是RGBA32位颜色:
#define STB_IMAGE_IMPLEMENTATION #include <stb_image.h> dnd::Image* Image::Create(string_view path_name) { int texWidth, texHeight, texChannels; stbi_uc* pixels = stbi_load(string{ path_name }.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); if (!pixels) { debug_err(format("加载图像失败:{}", path_name)); return nullptr; } Image* ret = new Image; ret->_data = (uint32_t*)pixels; ret->_size = { (unsigned)texWidth, (unsigned)texHeight }; return ret; }
导出图像数据更简单;
#define STB_IMAGE_WRITE_IMPLEMENTATION #include <stb_image_write.h> bool Image::SaveToFile(string_view path_name) { return stbi_write_png(string{ path_name }.c_str(), _size[0], _size[1], 4, _data, 0); }
注意,我们应该使用它的释放函数来释放资源,转回stbi_uc*类型(实际上也就是调用了free函数,不过大家都懂不要自己free):
stbi_image_free((stbi_uc*)_data);
四、FreeImage
代码中用到的一些函数(语法糖)如下:
/** * @brief RAII清理操作 */ template<typename F> class finally { public: finally(F&& func) : _func(func) {} ~finally() { _func(); } private: F _func; }; //! 对容器任意元素的判断 namespace Any { /** * @brief 判断容器任意元素等于某值 * @param[in] container 操作的容器 * @param[in] v 要比较的值 */ template<typename C, typename V> bool Equal(const C& container, const V& v) { for (auto& iter : container) { if (iter == v) return true; } return false; } }
读取代码如下:
#include <FreeImage.h> string path_name_mb = String::cvt_u8_mb(path_name); const char* filename = path_name_mb.c_str(); //读文件头判断格式 FREE_IMAGE_FORMAT file_format = FreeImage_GetFileType(filename, 0); if (file_format == FIF_UNKNOWN) {//通过文件名读取 file_format = FreeImage_GetFIFFromFilename(filename); } if (file_format == FIF_UNKNOWN) { debug_err("图像文件格式不支持:" + string{ path_name }); return nullptr; } //格式支持读取则读取 FIBITMAP* bitmap = nullptr; if (FreeImage_FIFSupportsReading(file_format)) { bitmap = FreeImage_Load(file_format, filename); } if (!bitmap) { return nullptr; } //资源释放 finally f0([&]() { FreeImage_Unload(bitmap); }); // unsigned bpp = FreeImage_GetBPP(bitmap); //取像素深度 FREE_IMAGE_TYPE file_type = FreeImage_GetImageType(bitmap); //取数据类型 BYTE* bits = FreeImage_GetBits(bitmap); //取像素数组 unsigned w = FreeImage_GetWidth(bitmap); //宽 unsigned h = FreeImage_GetHeight(bitmap); //高 unsigned pitch = FreeImage_GetPitch(bitmap); //每行像素(freeimage自动做了32位对齐, gl默认也是32位对齐) if (bits == 0 || w == 0 || h == 0) { debug_err("图像文件基本数据错误:" + string{ path_name }); return nullptr; } // if (file_type != FIT_BITMAP) { debug_err("图像文件类型不是位图:" + string{ path_name }); return nullptr; } // vector<unsigned> mul_bpp{ 32, 24, 8 }; if (!Any::Equal(mul_bpp, bpp)) { debug_err(format("图像文件不支持的色深({}):", bpp, path_name)); return nullptr; } // Image* ret = new Image; ret->_size = { w, h }; size_t length = size_t(w * h); ret->_buffer = new uint32[length]; //BGRA => ABGR(RGBA) 单个访问时有字节序问题,需要反过来 if (bpp == 32) { for (unsigned x = 0; x < w; ++x) { for (unsigned y = 0; y < h; ++y) { unsigned p0 = y * w + x; unsigned p1 = (h - 1 - y) * pitch + x * 4; char* p = (char*)&(ret->_buffer[p0]); p[0] = bits[p1 + 2]; p[1] = bits[p1 + 1]; p[2] = bits[p1 + 0]; p[3] = bits[p1 + 3]; } } } else if (bpp == 24) { for (unsigned x = 0; x < w; x++) { for (unsigned y = 0; y < h; y++) { unsigned p0 = y * w + x; unsigned p1 = (h - 1 - y) * pitch + x * 3; char* p = (char*)&(ret->_buffer[p0]); p[0] = bits[p1 + 2]; p[1] = bits[p1 + 1]; p[2] = bits[p1 + 0]; p[3] = (char)255; } } } else if (bpp == 8) { for (unsigned x = 0; x < w; x++) { for (unsigned y = 0; y < h; y++) { unsigned p0 = y * w + x; unsigned p1 = (h - 1 - y) * pitch + x * 1; char* p = (char*)&(ret->_buffer[p0]); p[0] = bits[p1]; p[1] = bits[p1]; p[2] = bits[p1]; p[3] = (char)255; } } } return ret;
导出图像数据到文件:
string path_name_mb = String::cvt_u8_mb(path_name); const char* filename = path_name_mb.c_str(); FREE_IMAGE_FORMAT file_format = FreeImage_GetFIFFromFilename(filename); if (file_format == FIF_UNKNOWN) { debug_err("不支持的图像格式:" + string{ path_name }); return false; } unsigned w = _size[0]; unsigned h = _size[1]; uint32_t* bits = new uint32_t[w * h]; //ABGR(RGBA)=> BGRA 单个访问时有字节序问题,需要反过来 for (unsigned x = 0; x < w; ++x) { for (unsigned y = 0; y < h; ++y) { unsigned index = y * w + x; char* p_dst = (char*)&(bits[index]); char* p = (char*)&(_buffer[index]); p_dst[0] = p[2]; p_dst[1] = p[1]; p_dst[2] = p[0]; p_dst[3] = p[3]; } } // FIBITMAP* bitmap = FreeImage_ConvertFromRawBits((BYTE*)bits, w, h, w * 4, 32, 0, 0, 0, true); delete[] bits; FreeImage_Save(file_format, bitmap, filename); //释放 FreeImage_Unload(bitmap); return true;
五、libpng
这里的代码比较陈旧,用了win32的类型,自行修改一下即可。加载代码如下:
#include <png.h> Image* img = new Image; ///从文件加载/ FILE* fp = NULL; if (sys->GetFile(path, fp) == -1) { debug_err(String(L"DND: Image::Create: 图像文件打开失败: ") + path); return NULL; } //判断是否问 png 文件 size_t number = 8; png_bytep header = new png_byte[number]; fread(header, 1, number, fp); bool is_png = !png_sig_cmp(header, 0, number); if (!is_png) { fclose(fp); debug_err(String(L"DND: Image::Create: 必须是png文件: ") + path); return NULL; } //初始化pnglib png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { fclose(fp); debug_err(L"DND: Image::Create: 初始化pnglib失败!"); return NULL; } //创建图像信息 info png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { fclose(fp); debug_err(L"DND: Image::Create: 创建png_info失败!"); return NULL; } //错误处理 if (setjmp(png_jmpbuf(png_ptr))) { fclose(fp); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); debug_err(L"DND: Image::Create: pnglib出现错误!"); return NULL; } //设置数据源 png_init_io(png_ptr, fp); //表明文件头已处理 png_set_sig_bytes(png_ptr, number); //读png 这一步会实际分配内存 png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0); fclose(fp); //从info查询数据 unsigned w = png_get_image_width(png_ptr, info_ptr); //获得图片宽度 unsigned h = png_get_image_height(png_ptr, info_ptr); //获得图片高度 int color_type = png_get_color_type(png_ptr, info_ptr); //获得图片颜色类型 //赋值image img->_size.w = w; img->_size.h = h; img->_buffer = new DWORD[w*h]; //从info 复制到 image png_bytep *row_point = NULL; row_point = png_get_rows(png_ptr, info_ptr); int block_size = (color_type == 6 ? 4 : 3); //(A)RGB unsigned pos = 0; for (unsigned x = 0; x < h; ++x) for (unsigned y = 0; y < w*block_size; y += block_size) { ((unsigned char*)img->_buffer)[pos + 0] = row_point[x][y + 2];//b; ((unsigned char*)img->_buffer)[pos + 1] = row_point[x][y + 1];//g ((unsigned char*)img->_buffer)[pos + 2] = row_point[x][y + 0];//r if (color_type == 6) ((unsigned char*)img->_buffer)[pos + 3] = row_point[x][y + 3];//a else ((unsigned char*)img->_buffer)[pos + 3] = 0xff; pos += 4; } //释放png内存 png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return img;
导出图像数据到文件:
FILE* fp; png_infop info_ptr; char cpath[MAX_PATH] = { NULL }; path.GetMultiByteStr(cpath, MAX_PATH); fopen_s(&fp, cpath, "wb"); if (fp == NULL) { debug_err(L"DND: Image::SaveToPNG: 创建文件失败!"); return; } //初始化pnglib static png_structp png_ptr = NULL; if (!png_ptr) png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { debug_err(L"DND: Image::SaveToPNG: 创建文件时初始化pnglib失败!"); return; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { fclose(fp); debug_err(L"DND: Image::SaveToPNG: png_create_info_struct失败!"); return; } //错误处理 if (setjmp(png_jmpbuf(png_ptr))) { fclose(fp); png_destroy_write_struct(&png_ptr, (png_infopp)NULL); debug_err(L"DND: Image::SaveToPNG: pnglib 出现错误!"); return; } unsigned bit_depth = 8; unsigned pixel_byte = 4; unsigned row_byte = _size.w * pixel_byte; //设置输出控制 png_init_io(png_ptr, fp); //设置图像属性 png_set_IHDR(png_ptr, info_ptr, _size.w, _size.h, bit_depth, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, //交错无 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); //写头部 png_write_info(png_ptr, info_ptr); //获取行指针 png_bytepp row_pointers = (png_bytep*)malloc(_size.h*sizeof(png_bytep)); for (unsigned x = 0; x < _size.h; ++x) {//分配一行 row_pointers[x] = (png_bytep)malloc(row_byte); for (unsigned y = 0; y < row_byte; y += pixel_byte) { row_pointers[x][y + 2] = ((unsigned char*)_buffer)[x * row_byte + y + 0]; row_pointers[x][y + 1] = ((unsigned char*)_buffer)[x * row_byte + y + 1]; row_pointers[x][y + 0] = ((unsigned char*)_buffer)[x * row_byte + y + 2]; row_pointers[x][y + 3] = ((unsigned char*)_buffer)[x * row_byte + y + 3]; /*row_pointers[x][y + 2] = row_pointers[x][y + 1] = row_pointers[x][y + 0] = row_pointers[x][y + 3] = 0xff;*/ } } //写入全部 png_write_image(png_ptr, row_pointers); //写尾部 png_write_end(png_ptr, info_ptr); //释放png内存 png_destroy_write_struct(&png_ptr, (png_infopp) NULL); /* delete[] row_pointers; delete[] image;*/ for (unsigned x = 0; x < _size.h; ++x) {//释放每行 free(row_pointers[x]); } free(row_pointers); fclose(fp);
到此这篇关于C++图像加载之libpng、FreeImage、stb_image详解的文章就介绍到这了,更多相关C++图像加载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!