C图像库stb_image

本文介绍stb_image库的使用方法,包括加载、处理和保存多种格式的图片,并提供了一个反色处理的例子。该库支持JPG、PNG、TGA等格式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

stb_image下载

https://github.com/nothings/stb

stb_image.h用于解析图片格式:

JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC

stb_image_write.h用于保存图片格式:

PNG, TGA, BMP, HDR

示例

具体流程为:

加载图片->算法处理->保存图片->打开保存图片(仅Windows)

并对 加载,处理,保存 这三个环节都进行了耗时计算并输出。


#include <iostream>
#include <algorithm>

#include <cstdint>
#include <numeric>
#include <math.h>
#include <io.h>

//使用stbImage http://nothings.org/
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h" 

//如果是Windows的话,调用系统API ShellExecuteA打开图片
#if defined(_MSC_VER)
#include <windows.h> 
#define USE_SHELL_OPEN
#endif

//是否使用OMP方式计时
#define USE_OMP 0

#if  USE_OMP
#include <omp.h>
auto const epoch = omp_get_wtime();
double now() {
    return omp_get_wtime() - epoch; 
};
#else
#include <chrono>
auto const epoch = std::chrono::steady_clock::now();
double now() {
    return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - epoch).count() / 1000.0;
};
#endif

//计时函数
template<typename FN>
double bench(const FN &fn) {
    auto took = -now();
    return (fn(), took + now());
}

//存储当前传入文件位置的变量
std::string m_curFilePath;

//加载图片
void loadImage(const char* filename, unsigned char*& Output, int  &Width, int  &Height, int &Channels)
{
    Output = stbi_load(filename, &Width, &Height, &Channels, 0);
}
//保存图片
void saveImage(const char* filename, int  Width, int  Height, int Channels, unsigned char* Output, bool open = true)
{
    std::string saveFile = m_curFilePath;
    saveFile += filename;
    //保存为png,也可以调用stbi_write_bmp 保存为bmp
    stbi_write_png(saveFile.c_str(), Width, Height, Channels, Output, 0);

#ifdef USE_SHELL_OPEN
    if (open)
        ShellExecuteA(NULL, "open", saveFile.c_str(), NULL, NULL, SW_SHOW);
#else
    //其他平台暂不实现
#endif
}

//取当前传入的文件位置
void getCurrentFilePath(const char* filePath, std::string& curFilePath)
{
    char drive[_MAX_DRIVE];
    char dir[_MAX_DIR];
    char fname[_MAX_FNAME];
    char ext[_MAX_EXT];
    curFilePath.clear();
    _splitpath_s(filePath, drive, dir, fname, ext);
    curFilePath += drive;
    curFilePath += dir;
    curFilePath += fname;
    curFilePath += "_";
}

//算法处理,这里以一个反色作为例子
void processImage(unsigned char* Input, unsigned char* Output, unsigned int  Width, unsigned int  Height, unsigned int Channels)
{
    int WidthStep = Width*Channels;
    if (Channels == 1)
    {
        for (unsigned int Y = 0; Y < Height; Y++)
        {
            unsigned char*     pOutput = Output + (Y * WidthStep);
            unsigned char*     pInput = Input + (Y * WidthStep);
            for (unsigned int X = 0; X < Width; X++)
            {
                pOutput[0] = 255 - pInput[0];

                //下一个像素点
                pInput += Channels;
                pOutput += Channels;
            }
        }
    }
    else    if (Channels == 3 || Channels == 4)
    {
        for (unsigned int Y = 0; Y < Height; Y++)
        {
            unsigned char*     pOutput = Output + (Y * WidthStep);
            unsigned char*     pInput = Input + (Y * WidthStep);
            for (unsigned int X = 0; X < Width; X++)
            {
                pOutput[0] = 255 - pInput[0];
                pOutput[1] = 255 - pInput[1];
                pOutput[2] = 255 - pInput[2];
                //通道数为4时,不处理A通道反色(pOutput[3] = 255 - pInput[3];)
                //下一个像素点
                pInput += Channels;
                pOutput += Channels;
            }
        }
    }

}

//本人博客:http://tntmonks.cnblogs.com/转载请注明出处.

int main(int argc, char **argv) {

    std::cout << "Image Processing " << std::endl;
    std::cout << "Demo By Gaozhihan (Build 2016-03-22)" << std::endl;
    std::cout << "支持解析如下图片格式:" << std::endl;
    std::cout << "JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC" << std::endl;

    //检查参数是否正确
    if (argc < 2)
    {
        std::cout << "参数错误。" << std::endl;
        std::cout << "请拖放文件到可执行文件上,或使用命令行:imageProc.exe 图片" << std::endl;
        std::cout << "例如: imageProc.exe d:image.jpg" << std::endl;

        return 0;
    }

    std::string szfile = argv[1];
    //检查输入的文件是否存在
    if (_access(szfile.c_str(), 0) == -1)
    {
        std::cout << "输入的文件不存在,参数错误!" << std::endl;
    }

    getCurrentFilePath(szfile.c_str(), m_curFilePath);

    int Width = 0;        //图片宽度
    int Height = 0;        //图片高度
    int Channels = 0;    //图片通道数
    unsigned char* inputImage = NULL;    //输入图片指针

    double nLoadTime = bench([&]{
        //加载图片
        loadImage(szfile.c_str(), inputImage, Width, Height, Channels);
    });
    std::cout << " 加载耗时: " << int(nLoadTime * 1000) << " 毫秒" << std::endl;
    if ((Channels != 0) && (Width != 0) && (Height != 0))
    {
        //分配与载入同等内存用于处理后输出结果
        unsigned char* outputImg = (unsigned char*)STBI_MALLOC(Width*Channels*Height*sizeof(unsigned char));
        if (inputImage) {
            //如果图片加载成功,则将内容复制给输出内存,方便处理
            memcpy(outputImg, inputImage, Width*Channels*Height);
        }
        else     {
            std::cout << " 加载文件: 
" << szfile.c_str() << " 失败!" << std::endl;
        }

        double nProcessTime = bench([&]{
            //处理算法
            processImage(inputImage, outputImg, Width, Height, Channels);
        });
        std::cout << " 处理耗时: " << int(nProcessTime * 1000) << " 毫秒" << std::endl;

        //保存处理后的图片
        double nSaveTime = bench([&]{
            saveImage("_done.png", Width, Height, Channels, outputImg);
        });
        std::cout << " 保存耗时: " << int(nSaveTime * 1000) << " 毫秒" << std::endl;

        //释放占用的内存
        if (outputImg)
        {
            STBI_FREE(outputImg);
            outputImg = NULL;
        }

        if (inputImage)
        {
            STBI_FREE(inputImage);
            inputImage = NULL;
        }
    }
    else
    {
        std::cout << " 加载文件: 
" << szfile.c_str() << " 失败!" << std::endl;
    }

    getchar();
    std::cout << "按任意键退出程序 
" << std::endl;
    return 0;
}

实例2

#include <stdio.h>

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

int main(int argc, char** argv)
{
    int w, h, n;

    //rgba
    //load image
    unsigned char *data = stbi_load("rgba.png", &w, &h, &n, 0);

    printf("%d, %d, %d\n", w, h, n);

    //change pixel

    //rgba,write 10 red pixel at line 11
    for (int dx = 0; dx < 10; ++dx)
    {
        data[n * w * 10 + dx * n + 0] = 255;
        data[n * w * 10 + dx * n + 1] = 0;
        data[n * w * 10 + dx * n + 2] = 0;
        data[n * w * 10 + dx * n + 3] = 255;
    }


    //write image
    stbi_write_png("write.png", w, h, n, data, w * 4);

    stbi_image_free(data);


    return 0;
}

ref

https://blog.youkuaiyun.com/lg1259156776/article/details/73385373
https://www.cnblogs.com/kileyi/p/8000199.html

<think>好的,我现在需要解决用户关于如何正确使用stb_image库的问题,特别是头文件和宏定义的位置要求。用户想知道是否可以将宏定义和头文件直接放在主函数的源文件中。根据之前的回答和引用内容,我需要整理出正确的使用方法,并解释为什么这样做可行或不可行。首先,回顾之前的引用内容。引用[1]提到,必须在一个源文件中定义STB_IMAGE_IMPLEMENTATION宏,这样才能让头文件展开函数实现,否则会出现链接错误。同时,用户之前的问题中已经提到过,如果在多个文件中定义该宏,会导致重复定义的错误。因此,关键点在于确保宏只在一个文件中定义一次。用户现在的问题是,是否可以将这个宏定义放在主函数的源文件中(比如main.c)。根据引用[3]中的步骤,配置项目时需要在一个源文件中定义宏并包含头文件。因此,放在main.c中是可行的,但需要注意只能在一个文件中这样做。例如,在main.c的开头定义宏,然后包含头文件,这样编译器在编译main.c时会生成所需的函数实现,而其他文件只需要包含头文件而不定义宏。但用户可能存在的疑问是,这样做是否会影响代码结构或者导致其他问题。比如,如果项目中有多个源文件需要用到stb_image的功能,是否每个文件都需要包含头文件?答案是否定的,其他文件只需要包含头文件,但不需要再次定义宏,否则会导致重复定义。因此,将宏定义放在main.c中是可行的,但需要确保其他文件不重复定义。另外,引用[2]提到编译时的步骤,需要将各个源文件编译成对象文件,然后链接在一起。如果宏定义在main.c中,那么在编译main.c时会生成stb_image的实现代码,其他文件在链接时会正确引用这些函数,而不会出现未定义的错误。可能需要注意的问题包括:如果多个文件都包含头文件并定义了宏,会导致函数重复定义,这在GCC中会报错。因此,必须严格遵守只在主函数所在的文件中定义一次宏。总结来说,用户的问题的答案是可行的,但需要遵循单一实现原则。同时,需要提醒用户相关的注意事项,比如避免重复定义,正确链接数学库等。另外,示例代码可以帮助用户更好地理解如何组织代码结构,确保编译命令正确无误。</think>### stb_image 库头文件与宏定义的使用方法 #### 一、宏定义与头文件放置规则 1. **核心原则** 必须且只能在**一个源文件**中定义 `STB_IMAGE_IMPLEMENTATION` 宏,并在此之后包含 `stb_image.h`。这一操作会将头文件转换为函数实现的源码[^1][^3]。 2. **主函数源文件中的可行性** **允许**将宏定义和头文件直接放在 `main.c` 中,例如: ```c // main.c #define STB_IMAGE_IMPLEMENTATION // 仅在主文件中定义一次 #include "stb_image.h" #include <stdio.h> int main() { // 图像加载代码... } ``` - 此方式满足单一定义规则,避免 `multiple definition` 错误[^1] - 其他源文件只需普通包含头文件(无需重复定义宏) #### 二、代码组织对比 | **方式** | **优点** | **缺点** | |--------------------|---------------------------|---------------------------| | 宏定义在 `main.c` | 简单直观,适合小型项目 | 主文件可能变得臃肿 | | 独立实现文件 | 模块化清晰,便于扩展功能 | 需要额外管理源文件 | #### 三、典型错误场景 1. **重复定义宏** 若在多个文件中定义 `STB_IMAGE_IMPLEMENTATION`,会导致函数重复定义: ```bash gcc -o app main.c utils.c # utils.c 中也定义了宏 → 报错 ``` 错误信息示例: ``` multiple definition of 'stbi_load' ``` 2. **未链接数学库** 某些图像格式(如 PNG)需要数学库支持,需添加 `-lm` 参数: ```bash gcc -o app main.c -lm # 正确链接数学函数[^1] ``` #### 四、编译验证示例 1. **项目结构** ```text project/ ├── main.c // 包含宏定义和主逻辑 └── stb_image.h // 原始头文件 ``` 2. **编译命令** ```bash gcc -o image_loader main.c -lm ``` 3. **运行检测** 使用 Valgrind 检查内存泄漏: ```bash valgrind --leak-check=full ./image_loader ``` #### 五、扩展应用 若需同时使用 `stb_image_write.h`,需新建独立实现文件: ```c // stb_write_impl.c #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" ``` 编译时需包含所有实现文件: ```bash gcc -o image_tool main.c stb_write_impl.c -lm ``` --- ### 相关问题 1. 如何通过 CMake 管理包含 stb_image 的项目? 2. stb_image 加载不同格式图像时的性能差异如何优化? 3. 如何处理 stb_image 与其他图像库的符号冲突? 4. 如何为 stb_image 添加自定义文件读取接口?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值