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


    图像加载

    目录

    图像加载

    一、前言

    二、对比

    三、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位颜色,如下:

    1. class Image
    2. {
    3. //other
    4. uint32_t* _data;
    5. array<unsigned, 2> _size;
    6. }

            注意我并没有处理字节序问题,是写死的,在不同大小端的系统运行,应该改进一下代码(我懒得很,要等到遇到问题再解决,就是交换一下单个像素RGBA通道的读取顺序)。

            觉得有用,请点赞、收藏、关注。我写这篇文件就是因为有人get了我以前写的libpng的文章。

    三、stb_image

            读取如下,确实很简单,直接返回的就是RGBA32位颜色:

    1. #define STB_IMAGE_IMPLEMENTATION
    2. #include
    3. dnd::Image* Image::Create(string_view path_name)
    4. {
    5. int texWidth, texHeight, texChannels;
    6. stbi_uc* pixels = stbi_load(string{ path_name }.c_str(),
    7. &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
    8. if (!pixels)
    9. {
    10. debug_err(format("加载图像失败:{}", path_name));
    11. return nullptr;
    12. }
    13. Image* ret = new Image;
    14. ret->_data = (uint32_t*)pixels;
    15. ret->_size = { (unsigned)texWidth, (unsigned)texHeight };
    16. return ret;
    17. }

            导出图像数据更简单;

    1. #define STB_IMAGE_WRITE_IMPLEMENTATION
    2. #include
    3. bool Image::SaveToFile(string_view path_name)
    4. {
    5. return stbi_write_png(string{ path_name }.c_str(), _size[0], _size[1], 4, _data, 0);
    6. }

            注意,我们应该使用它的释放函数来释放资源,转回stbi_uc*类型(实际上也就是调用了free函数,不过大家都懂不要自己free):

    stbi_image_free((stbi_uc*)_data);

    四、FreeImage

            代码中用到的一些函数(语法糖)如下:

    1. /**
    2. * @brief RAII清理操作
    3. */
    4. template<typename F>
    5. class finally
    6. {
    7. public:
    8. finally(F&& func) : _func(func) {}
    9. ~finally() { _func(); }
    10. private:
    11. F _func;
    12. };
    13. //! 对容器任意元素的判断
    14. namespace Any
    15. {
    16. /**
    17. * @brief 判断容器任意元素等于某值
    18. * @param[in] container 操作的容器
    19. * @param[in] v 要比较的值
    20. */
    21. template<typename C, typename V>
    22. bool Equal(const C& container, const V& v)
    23. {
    24. for (auto& iter : container)
    25. {
    26. if (iter == v)
    27. return true;
    28. }
    29. return false;
    30. }
    31. }

            读取代码如下:

    1. #include
    2. string path_name_mb = String::cvt_u8_mb(path_name);
    3. const char* filename = path_name_mb.c_str();
    4. //读文件头判断格式
    5. FREE_IMAGE_FORMAT file_format = FreeImage_GetFileType(filename, 0);
    6. if (file_format == FIF_UNKNOWN)
    7. {//通过文件名读取
    8. file_format = FreeImage_GetFIFFromFilename(filename);
    9. }
    10. if (file_format == FIF_UNKNOWN)
    11. {
    12. debug_err("图像文件格式不支持:" + string{ path_name });
    13. return nullptr;
    14. }
    15. //格式支持读取则读取
    16. FIBITMAP* bitmap = nullptr;
    17. if (FreeImage_FIFSupportsReading(file_format))
    18. {
    19. bitmap = FreeImage_Load(file_format, filename);
    20. }
    21. if (!bitmap)
    22. {
    23. return nullptr;
    24. }
    25. //资源释放
    26. finally f0([&]() {
    27. FreeImage_Unload(bitmap);
    28. });
    29. //
    30. unsigned bpp = FreeImage_GetBPP(bitmap); //取像素深度
    31. FREE_IMAGE_TYPE file_type = FreeImage_GetImageType(bitmap); //取数据类型
    32. BYTE* bits = FreeImage_GetBits(bitmap); //取像素数组
    33. unsigned w = FreeImage_GetWidth(bitmap); //宽
    34. unsigned h = FreeImage_GetHeight(bitmap); //高
    35. unsigned pitch = FreeImage_GetPitch(bitmap); //每行像素(freeimage自动做了32位对齐, gl默认也是32位对齐)
    36. if (bits == 0 || w == 0 || h == 0)
    37. {
    38. debug_err("图像文件基本数据错误:" + string{ path_name });
    39. return nullptr;
    40. }
    41. //
    42. if (file_type != FIT_BITMAP)
    43. {
    44. debug_err("图像文件类型不是位图:" + string{ path_name });
    45. return nullptr;
    46. }
    47. //
    48. vector<unsigned> mul_bpp{ 32, 24, 8 };
    49. if (!Any::Equal(mul_bpp, bpp))
    50. {
    51. debug_err(format("图像文件不支持的色深({}):", bpp, path_name));
    52. return nullptr;
    53. }
    54. //
    55. Image* ret = new Image;
    56. ret->_size = { w, h };
    57. size_t length = size_t(w * h);
    58. ret->_buffer = new uint32[length];
    59. //BGRA => ABGR(RGBA) 单个访问时有字节序问题,需要反过来
    60. if (bpp == 32)
    61. {
    62. for (unsigned x = 0; x < w; ++x)
    63. {
    64. for (unsigned y = 0; y < h; ++y)
    65. {
    66. unsigned p0 = y * w + x;
    67. unsigned p1 = (h - 1 - y) * pitch + x * 4;
    68. char* p = (char*)&(ret->_buffer[p0]);
    69. p[0] = bits[p1 + 2];
    70. p[1] = bits[p1 + 1];
    71. p[2] = bits[p1 + 0];
    72. p[3] = bits[p1 + 3];
    73. }
    74. }
    75. }
    76. else if (bpp == 24)
    77. {
    78. for (unsigned x = 0; x < w; x++)
    79. {
    80. for (unsigned y = 0; y < h; y++)
    81. {
    82. unsigned p0 = y * w + x;
    83. unsigned p1 = (h - 1 - y) * pitch + x * 3;
    84. char* p = (char*)&(ret->_buffer[p0]);
    85. p[0] = bits[p1 + 2];
    86. p[1] = bits[p1 + 1];
    87. p[2] = bits[p1 + 0];
    88. p[3] = (char)255;
    89. }
    90. }
    91. }
    92. else if (bpp == 8)
    93. {
    94. for (unsigned x = 0; x < w; x++)
    95. {
    96. for (unsigned y = 0; y < h; y++)
    97. {
    98. unsigned p0 = y * w + x;
    99. unsigned p1 = (h - 1 - y) * pitch + x * 1;
    100. char* p = (char*)&(ret->_buffer[p0]);
    101. p[0] = bits[p1];
    102. p[1] = bits[p1];
    103. p[2] = bits[p1];
    104. p[3] = (char)255;
    105. }
    106. }
    107. }
    108. return ret;

            导出图像数据到文件:

    1. string path_name_mb = String::cvt_u8_mb(path_name);
    2. const char* filename = path_name_mb.c_str();
    3. FREE_IMAGE_FORMAT file_format = FreeImage_GetFIFFromFilename(filename);
    4. if (file_format == FIF_UNKNOWN)
    5. {
    6. debug_err("不支持的图像格式:" + string{ path_name });
    7. return false;
    8. }
    9. unsigned w = _size[0];
    10. unsigned h = _size[1];
    11. uint32_t* bits = new uint32_t[w * h];
    12. //ABGR(RGBA)=> BGRA 单个访问时有字节序问题,需要反过来
    13. for (unsigned x = 0; x < w; ++x)
    14. {
    15. for (unsigned y = 0; y < h; ++y)
    16. {
    17. unsigned index = y * w + x;
    18. char* p_dst = (char*)&(bits[index]);
    19. char* p = (char*)&(_buffer[index]);
    20. p_dst[0] = p[2];
    21. p_dst[1] = p[1];
    22. p_dst[2] = p[0];
    23. p_dst[3] = p[3];
    24. }
    25. }
    26. //
    27. FIBITMAP* bitmap = FreeImage_ConvertFromRawBits((BYTE*)bits,
    28. w, h, w * 4, 32, 0, 0, 0, true);
    29. delete[] bits;
    30. FreeImage_Save(file_format, bitmap, filename);
    31. //释放
    32. FreeImage_Unload(bitmap);
    33. return true;

    五、libpng

            这里的代码比较陈旧,用了win32的类型,自行修改一下即可。加载代码如下:

    1. #include
    2. Image* img = new Image;
    3. ///从文件加载/
    4. FILE* fp = NULL;
    5. if (sys->GetFile(path, fp) == -1)
    6. {
    7. debug_err(String(L"DND: Image::Create: 图像文件打开失败: ") + path);
    8. return NULL;
    9. }
    10. //判断是否问 png 文件
    11. size_t number = 8;
    12. png_bytep header = new png_byte[number];
    13. fread(header, 1, number, fp);
    14. bool is_png = !png_sig_cmp(header, 0, number);
    15. if (!is_png)
    16. {
    17. fclose(fp);
    18. debug_err(String(L"DND: Image::Create: 必须是png文件: ") + path);
    19. return NULL;
    20. }
    21. //初始化pnglib
    22. png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
    23. NULL, NULL, NULL);
    24. if (!png_ptr)
    25. {
    26. fclose(fp);
    27. debug_err(L"DND: Image::Create: 初始化pnglib失败!");
    28. return NULL;
    29. }
    30. //创建图像信息 info
    31. png_infop info_ptr = png_create_info_struct(png_ptr);
    32. if (!info_ptr)
    33. {
    34. fclose(fp);
    35. debug_err(L"DND: Image::Create: 创建png_info失败!");
    36. return NULL;
    37. }
    38. //错误处理
    39. if (setjmp(png_jmpbuf(png_ptr)))
    40. {
    41. fclose(fp);
    42. png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    43. debug_err(L"DND: Image::Create: pnglib出现错误!");
    44. return NULL;
    45. }
    46. //设置数据源
    47. png_init_io(png_ptr, fp);
    48. //表明文件头已处理
    49. png_set_sig_bytes(png_ptr, number);
    50. //读png 这一步会实际分配内存
    51. png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);
    52. fclose(fp);
    53. //从info查询数据
    54. unsigned w = png_get_image_width(png_ptr, info_ptr); //获得图片宽度
    55. unsigned h = png_get_image_height(png_ptr, info_ptr); //获得图片高度
    56. int color_type = png_get_color_type(png_ptr, info_ptr); //获得图片颜色类型
    57. //赋值image
    58. img->_size.w = w;
    59. img->_size.h = h;
    60. img->_buffer = new DWORD[w*h];
    61. //从info 复制到 image
    62. png_bytep *row_point = NULL;
    63. row_point = png_get_rows(png_ptr, info_ptr);
    64. int block_size = (color_type == 6 ? 4 : 3);
    65. //(A)RGB
    66. unsigned pos = 0;
    67. for (unsigned x = 0; x < h; ++x)
    68. for (unsigned y = 0; y < w*block_size; y += block_size)
    69. {
    70. ((unsigned char*)img->_buffer)[pos + 0] = row_point[x][y + 2];//b;
    71. ((unsigned char*)img->_buffer)[pos + 1] = row_point[x][y + 1];//g
    72. ((unsigned char*)img->_buffer)[pos + 2] = row_point[x][y + 0];//r
    73. if (color_type == 6)
    74. ((unsigned char*)img->_buffer)[pos + 3] = row_point[x][y + 3];//a
    75. else
    76. ((unsigned char*)img->_buffer)[pos + 3] = 0xff;
    77. pos += 4;
    78. }
    79. //释放png内存
    80. png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    81. return img;

            导出图像数据到文件:

    1. FILE* fp;
    2. png_infop info_ptr;
    3. char cpath[MAX_PATH] = { NULL };
    4. path.GetMultiByteStr(cpath, MAX_PATH);
    5. fopen_s(&fp, cpath, "wb");
    6. if (fp == NULL)
    7. {
    8. debug_err(L"DND: Image::SaveToPNG: 创建文件失败!");
    9. return;
    10. }
    11. //初始化pnglib
    12. static png_structp png_ptr = NULL;
    13. if (!png_ptr)
    14. png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
    15. NULL, NULL, NULL);
    16. if (!png_ptr)
    17. {
    18. debug_err(L"DND: Image::SaveToPNG: 创建文件时初始化pnglib失败!");
    19. return;
    20. }
    21. info_ptr = png_create_info_struct(png_ptr);
    22. if (info_ptr == NULL)
    23. {
    24. fclose(fp);
    25. debug_err(L"DND: Image::SaveToPNG: png_create_info_struct失败!");
    26. return;
    27. }
    28. //错误处理
    29. if (setjmp(png_jmpbuf(png_ptr)))
    30. {
    31. fclose(fp);
    32. png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
    33. debug_err(L"DND: Image::SaveToPNG: pnglib 出现错误!");
    34. return;
    35. }
    36. unsigned bit_depth = 8;
    37. unsigned pixel_byte = 4;
    38. unsigned row_byte = _size.w * pixel_byte;
    39. //设置输出控制
    40. png_init_io(png_ptr, fp);
    41. //设置图像属性
    42. png_set_IHDR(png_ptr, info_ptr, _size.w, _size.h, bit_depth,
    43. PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, //交错无
    44. PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
    45. //写头部
    46. png_write_info(png_ptr, info_ptr);
    47. //获取行指针
    48. png_bytepp row_pointers = (png_bytep*)malloc(_size.h*sizeof(png_bytep));
    49. for (unsigned x = 0; x < _size.h; ++x)
    50. {//分配一行
    51. row_pointers[x] = (png_bytep)malloc(row_byte);
    52. for (unsigned y = 0; y < row_byte; y += pixel_byte)
    53. {
    54. row_pointers[x][y + 2] = ((unsigned char*)_buffer)[x * row_byte + y + 0];
    55. row_pointers[x][y + 1] = ((unsigned char*)_buffer)[x * row_byte + y + 1];
    56. row_pointers[x][y + 0] = ((unsigned char*)_buffer)[x * row_byte + y + 2];
    57. row_pointers[x][y + 3] = ((unsigned char*)_buffer)[x * row_byte + y + 3];
    58. /*row_pointers[x][y + 2] =
    59. row_pointers[x][y + 1] =
    60. row_pointers[x][y + 0] =
    61. row_pointers[x][y + 3] = 0xff;*/
    62. }
    63. }
    64. //写入全部
    65. png_write_image(png_ptr, row_pointers);
    66. //写尾部
    67. png_write_end(png_ptr, info_ptr);
    68. //释放png内存
    69. png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
    70. /* delete[] row_pointers;
    71. delete[] image;*/
    72. for (unsigned x = 0; x < _size.h; ++x)
    73. {//释放每行
    74. free(row_pointers[x]);
    75. }
    76. free(row_pointers);
    77. fclose(fp);

  • 相关阅读:
    【Swift 60秒】31 - Repeat loops
    图神经网络详细内容
    矩阵论理论知识(二)
    解决开了burp suite ,火狐访问不了其他网站的问题
    上周热点回顾(6.27-7.3)
    MySQL 全文检索的实现
    互联网Java工程师面试题·MySQL 篇·第一弹
    《canvas》之第1章 canvas概述
    Elasticsearch - Elasticsearch 8.X;Elasticsearch 8.X集群(十)
    SpringBoot项目集成Dubbo
  • 原文地址:https://blog.csdn.net/u014629601/article/details/126794469