当前位置: 首页 > 工具软件 > FreeImage > 使用案例 >

【C++】图像加载(libpng、FreeImage、stb_image)

逄念
2023-12-01

图像加载

目录

图像加载

一、前言

二、对比

三、stb_image

四、FreeImage

五、libpng


一、前言

        libpng、FreeImage、stb_image都是图像解析的开源库,由于三者我都简单使用过,于是做个总结对比。

二、对比

开源协议编译依赖win编译难度使用难度格式支持
libpngzlibzlib自带vs工程只支持png
FreeImage混合自带了7、8个库有dll发行版简单很多
stb_imageMIT只有头文件简单常用的几个

        它们的官网如下:

libpng Home Page

The FreeImage Project

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);

 类似资料: