C++圖像加載之libpng、FreeImage、stb_image詳解
一、前言
libpng、FreeImage、stb_image都是圖像解析的開源庫,由于三者我都簡單使用過,于是做個總結(jié)對比。
二、對比
| 庫 | 開源協(xié)議 | 編譯依賴 | win編譯難度 | 使用難度 | 格式支持 |
| libpng | zlib | zlib | 自帶vs工程 | 中 | 只支持png |
| FreeImage | 混合 | 自帶了7、8個庫 | 有dll發(fā)行版 | 簡單 | 很多 |
| stb_image | MIT | 無 | 只有頭文件 | 簡單 | 常用的幾個 |
它們的官網(wǎng)如下:
GitHub - nothings/stb: stb single-file public domain libraries for C/C++
總的來說,它們的開源協(xié)議都問題不大。源碼編譯stb_image最簡單,因為它只有一個頭文件。而libpng是操作png文件的庫,代碼比較復(fù)雜,但是它的優(yōu)點是靈活,速度更快。FreeImage集成了各種加載庫,支持的格式比較多。
接下來,我就按使用難度給出它們的基本代碼。注意均是讀取到uint32_t緩沖區(qū),代表RGBA32位顏色,如下:
class Image
{
//other
uint32_t* _data;
array<unsigned, 2> _size;
}注意我并沒有處理字節(jié)序問題,是寫死的,在不同大小端的系統(tǒng)運行,應(yīng)該改進一下代碼(我懶得很,要等到遇到問題再解決,就是交換一下單個像素RGBA通道的讀取順序)。
覺得有用,請點贊、收藏、關(guān)注。我寫這篇文件就是因為有人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;
}導(dǎo)出圖像數(shù)據(jù)更簡單;
#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);
}注意,我們應(yīng)該使用它的釋放函數(shù)來釋放資源,轉(zhuǎn)回stbi_uc*類型(實際上也就是調(diào)用了free函數(shù),不過大家都懂不要自己free):
stbi_image_free((stbi_uc*)_data);
四、FreeImage
代碼中用到的一些函數(shù)(語法糖)如下:
/**
* @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); //取數(shù)據(jù)類型
BYTE* bits = FreeImage_GetBits(bitmap); //取像素數(shù)組
unsigned w = FreeImage_GetWidth(bitmap); //寬
unsigned h = FreeImage_GetHeight(bitmap); //高
unsigned pitch = FreeImage_GetPitch(bitmap); //每行像素(freeimage自動做了32位對齊, gl默認(rèn)也是32位對齊)
if (bits == 0 || w == 0 || h == 0)
{
debug_err("圖像文件基本數(shù)據(jù)錯誤:" + 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) 單個訪問時有字節(jié)序問題,需要反過來
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;導(dǎo)出圖像數(shù)據(jù)到文件:
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 單個訪問時有字節(jié)序問題,需要反過來
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;
}
//創(chuàng)建圖像信息 info
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
fclose(fp);
debug_err(L"DND: Image::Create: 創(chuàng)建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出現(xiàn)錯誤!");
return NULL;
}
//設(shè)置數(shù)據(jù)源
png_init_io(png_ptr, fp);
//表明文件頭已處理
png_set_sig_bytes(png_ptr, number);
//讀png 這一步會實際分配內(nèi)存
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);
fclose(fp);
//從info查詢數(shù)據(jù)
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 復(fù)制到 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內(nèi)存
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return img;導(dǎo)出圖像數(shù)據(jù)到文件:
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: 創(chuàng)建文件失?。?);
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: 創(chuàng)建文件時初始化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 出現(xiàn)錯誤!");
return;
}
unsigned bit_depth = 8;
unsigned pixel_byte = 4;
unsigned row_byte = _size.w * pixel_byte;
//設(shè)置輸出控制
png_init_io(png_ptr, fp);
//設(shè)置圖像屬性
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內(nèi)存
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);到此這篇關(guān)于C++圖像加載之libpng、FreeImage、stb_image詳解的文章就介紹到這了,更多相關(guān)C++圖像加載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過先序遍歷和中序遍歷后的序列還原二叉樹(實現(xiàn)方法)
下面小編就為大家?guī)硪黄ㄟ^先序遍歷和中序遍歷后的序列還原二叉樹(實現(xiàn)方法)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06
select函數(shù)實現(xiàn)高性能IO多路訪問的關(guān)鍵示例深入解析
這篇文章主要為大家介紹了select函數(shù)實現(xiàn)高性能IO多路訪問的關(guān)鍵示例深入解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09
QT委托代理機制之Model?View?Delegate使用方法詳解
這篇文章主要介紹了QT委托代理機制之Model?View?Delegate的使用方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
一文詳解C語言中的switch語句和while循環(huán)
這篇文章主要給大家詳細介紹了C語言中的switch語句和while循環(huán),文中通過代碼示例給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-12-12

