目录

1、图片灰化的实现思路

2、开源STB库下载

3、将图片文件的内容读到buffer中

4、将buffer中存放的图片文件数据传入到stbi_load_from_memory接口中,然后对返回的图片颜色值进行灰化处理

5、调用stbi_write_png或stbi_write_jpg接口将灰化后的图片数据保存成图片文件

6、图片灰化的完整代码


       前一篇文章我们讲到了使用libcurl库发http/https请求去下载用户头像文件(文章链接:https://blog.csdn.net/chenlycly/article/details/149175549,本篇文章则是同个SDK项目的后续需求中涉及到的功能。第三方厂商要求,对于不在线的人员,要显示灰化的头像。经研究决定使用开源STB库辅助实现图片灰化,调用STB开源库中的stbi_load_from_memorystbi_write_pngstbi_write_jpg等接口。本文详细讲述一下实现过程,以供大家借鉴或参考。

C++软件异常排查从入门到精通系列教程(核心精品专栏,订阅量已达8000多个,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新500多篇,订阅量已达6000多个,欢迎订阅,持续更新中...)https://blog.csdn.net/chenlycly/article/details/140824370C++ 软件开发从入门到实战(重点专栏,专栏文章已更新300多篇,欢迎订阅,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html

1、图片灰化的实现思路

       先将图片文件内容读到内存buffer中,然后将buffer中的图片内容传入到stbi_load_from_memory接口中,该接口返回图片的RGBA像素颜色值以及图片的宽高等信息,然后对像素颜色R、G、B、Alpha值乘以一个0-1之间的一个小数进行灰化处理:(值越小,灰化越明显,图像越黑)

R = R* 0.6;
G = G* 0.6;
B = B* 0.6;
Alpha = Alpha* 0.6;

将图片灰化后,再调用stbi_write_png或stbi_write_jpg接口将灰化后的图片数据写到磁盘文件中,保存成图片文件。

       关于图片灰化的实例,比如原始图片如下:

​灰化后的效果如下:

​注意,灰化后的仍然要显示图片的多彩颜色,不能是单一的黑白颜色。

2、开源STB库下载

        开源项目 nothings/stb 是由开发者 Sean T. Barrett 创建的一个轻量级、单文件头文件库集合(以作者名字缩写命名),托管在 GitHub 上(项目地址:https://github.com/nothings/stb)。

       nothings/stb 是一组 “极简主义”工具库,以单文件头文件形式(集成时将头文件加到工程中,所有功能函数的实现都放在头文件中)提供图像处理、字体渲染、数据结构等基础功能,适合追求轻量、快速集成的 C/C++ 开发者(集成时直接包含对应的头文件即可)。其设计哲学是 “用最简代码解决常见需求”,尤其适用于游戏开发、嵌入式系统或工具链构建。

       代码中要调用开源STB库的stbi_load_from_memory(位于头文件stb_image.h中)、stbi_write_png(位于头文件stb_image_write.h中)等接口,只要到https://github.com/nothings/stb中将stb_image.h和stb_image_write.h下载下来,添加到我们项目工程中即可:

​        在调用上述头文件中的接口的cpp文件中,包含stb_image.h和stb_image_write.h头文件,然后定义的如下的宏就能使用了:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

3、将图片文件的内容读到buffer中

      调用系统API接口CreateFile、ReadFile等将图片文件读到buffer中,相关代码如下所示:

CString strLog;

// 打开图片文件
HANDLE handle = CreateFile(strSrcFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == handle)
{
    DWORD dwLastError = GetLastError();
    strLog.Format(_T("[CreateGrayHeadPic] 打开图片文件失败:%s,GetLastError:%d."),
        strSrcFilePath, dwLastError);
    WriteLog(strLog);
    return;
}

// 获取图片文件大小,申请该大小的buffer内存
DWORD dwFileSize = GetFileSize(handle, NULL);
DWORD dwReadBytes = 0;
LPBYTE pData = (LPBYTE)malloc(dwFileSize);
DWORD dwBuffSize = dwFileSize;
if (NULL == pData)
{
    strLog.Format(_T("[CreateGrayHeadPic] 申请用于存放文件内容的内存失败,内存大小:%d."),
        dwFileSize);
    WriteLog(strLog);
    CloseHandle(handle);
    return;
}

// 将图片文件中的内容读到buffer中
if (!ReadFile(handle, pData, dwFileSize, &dwReadBytes, NULL))
{
    WriteLog(_T("[CreateGrayHeadPic] 调用ReadFile失败,读取源图片文件失败."));

    CloseHandle(handle);
    free(pData);
    pData = NULL;
    return;
}

CloseHandle(handle);

4、将buffer中存放的图片文件数据传入到stbi_load_from_memory接口中,然后对返回的图片颜色值进行灰化处理

       上面将图片文件内容读到内存中,要对图片进行灰化处理,则要拿到图片中RGB颜色值及宽高信息,只要调用stbi_load_from_memory接口,将图片文件内容传进去就可以获取到了,相关代码如下所示:

// * 描述 - 载入各种图片格式文件
// * 函数 -
// stbi_load_from_memory()
// 功能 - 加载图片
// buffer - 内存数据
// len - 数据大小,
// x - [out]图像宽度(像素)
// y - [out]图像高度(像素)
// comp - [out]图像数据组成结果
// req_comp - 图像数据组成
// STBI_default = 0, // only used for req_comp
// STBI_grey    = 1,
// STBI_grey_alpha = 2,
// STBI_rgb    = 3,
// STBI_rgb_alpha = 4
LPBYTE pImage = NULL;
int n = 0;
int nWidth = 0;
int nHeight = 0;
pImage = stbi_load_from_memory(pData, dwFileSize, &nWidth, &nHeight, &n, 4);
if (!pImage)
{
    const char* pFailReason = stbi_failure_reason();
    strLog.Format(_T("[CreateGrayHeadPic] 调用stbi_load_from_memory失败,原因:%s."),
        CopyCharToCStringT(pFailReason));
    WriteLog(strLog);    

    CloseHandle(handle);
    free(pData);
    return;
}

接口stbi_load_from_memory返回后,图片的宽高在参数nWidth和nHeight中返回,图片的RGB颜色值作为函数的返回值返回。

       以前在开发屏幕截图功能时,做过图片灰化处理,只需要给R、G、B、Alpha值乘以一个0-1之间的一个小数即可,比如:(该小数值越小,就越黑)

R = R* 0.6;
G = G* 0.6;
B = B* 0.6;
Alpha = Alpha* 0.6;

相关代码如下所示:

LPBYTE pDest = (LPBYTE)malloc(nWidth * nHeight * 4);
if (NULL == pDest)
{
    strLog.Format(_T("[CreateGrayHeadPic] 申请pDest内存失败,内存大小:%d."),
        nWidth * nHeight * 4);
    WriteLog(strLog);
    return;
}

for (int i = 0; i < nWidth * nHeight; i++)
{
    pDest[i * 4 + 3] = pImage[i * 4 + 3];
    if (pDest[i * 4 + 3] < 255)
    {
        // 先将RGB值预乘alpha通道值
        pDest[i * 4] = (BYTE)(DWORD(pImage[i * 4])*pImage[i * 4 + 3] / 255) * 0.6;
        pDest[i * 4 + 1] = (BYTE)(DWORD(pImage[i * 4 + 1])*pImage[i * 4 + 3] / 255) * 0.6;
        pDest[i * 4 + 2] = (BYTE)(DWORD(pImage[i * 4 + 2])*pImage[i * 4 + 3] / 255) * 0.6;
    }
    else
    {
        pDest[i * 4] = pImage[i * 4] * 0.6;
        pDest[i * 4 + 1] = pImage[i * 4 + 1] * 0.6;
        pDest[i * 4 + 2] = pImage[i * 4 + 2] * 0.6;
    }
}

       注意,对于包含透明区域alpha通道的png图片,需要做个预乘处理,否则灰化后的透明区域将失效。灰化后的图片效果如下:


       在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)

专栏1:该精品技术专栏的订阅量已达到10000多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,已经更新到210篇以上!欢迎订阅!)

C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法详细讲述了C++软件的调试方法与手段详细介绍分析C++软件问题的常用分析工具,以图文并茂的方式给出具体的项目问题实战分析实例(详细讲述分析排查过程,很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到300篇以上!

专栏2:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达8000多个,专栏文章已经更新到500多篇,持续更新中...)

C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法C++11及以上新特性(开源代码中可能会用到很多新特性(比如WebRTC开源库),日常编码中也会用到部分新特性,面试时也会频繁地涉及到,学习新特性很有必要)、常用C++开源库的介绍与使用(比如SQLite、libcurl、libwebsockets、libevent、jsoncpp/RapidJson、Redis、RabbitMQ、MongoDB、MQTT、ZooKeeper、OpenCV、FFmpeg、SDL、GStreamer、Live555、ReactOS等)、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(引发C++软件异常的常见原因分析与总结、排查C++软件异常的手段与方法、分析C++软件异常的基础知识、使用常用软件分析工具分析C++软件问题、多个项目实战问题分析案例分享等)、设计模式(单例模式、工厂模式、观察者模式、状态模式等)、网络基础知识与网络问题分析进阶内容(实战问题分析实例分享)等。本专栏的内容都是建立在项目实践的基础上,来源于项目实战,服务于项目实战,很有实战参考价值!

专栏3:  

C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795

常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!

专栏4:   

VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585

将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。

专栏5: 

C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html

根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。


5、调用stbi_write_png或stbi_write_jpg接口将灰化后的图片数据保存成图片文件

       对灰化后的像素颜色值调用stbi_write_png或stbi_write_jpg接口,就可以保存成灰化后的图片了。如果原图是包含透明区域的png图片,要保留透明区域,需要调用stbi_write_png接口保存成png图片(png图片才能保存透明通道)。对于不包含透明区域的其他类型图片(比如jpg、bmp等),则直接调用stbi_write_jpg接口保存成jpg图片即可。

// png图片有透明通道,如果原图是png,则需要调用stbi_write_png接口;如果原图是其他类型,
// 则统一转成jpg,调用stbi_write_jpg
int nRet = 0;
if (strGrayFilePath.Right(4) == _T(".png"))
{
    nRet = stbi_write_png(achGrayFilePath, nWidth, nHeight, 4, pDest, nWidth * 4);
}
else
{
    nRet = stbi_write_jpg(achGrayFilePath, nWidth, nHeight, 4, pDest, 90);
}

       对于stbi_write_png接口

STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void  *data, int stride_in_bytes);

最后一个参数stride_in_bytes,是图片宽度乘以通道数,如果要包含alpha通道,则通道数为4。

       对于stbi_write_jpg接口

STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality)

最后一个参数quality,是图像质量,取值范围是1-100,对于jpg图片,一般取90即可。

6、图片灰化的完整代码

      处理图片黑化的完整代码如下所示:

void CVideoCtrlLogic::CreateGrayHeadPic(const CString& strSrcFilePath, const CString& strGrayFilePath)
{
    CString strLog;

    // 打开图片文件
    HANDLE handle = CreateFile(strSrcFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == handle)
    {
        DWORD dwLastError = GetLastError();
        strLog.Format(_T("[CreateGrayHeadPic] 打开图片文件失败:%s,GetLastError:%d."),
            strSrcFilePath, dwLastError);
        WriteLog(strLog);
        return;
    }

    // 获取图片文件大小,申请该大小的buffer内存
    DWORD dwFileSize = GetFileSize(handle, NULL);
    DWORD dwReadBytes = 0;
    LPBYTE pData = (LPBYTE)malloc(dwFileSize);
    DWORD dwBuffSize = dwFileSize;
    if (NULL == pData)
    {
        strLog.Format(_T("[CreateGrayHeadPic] 申请用于存放文件内容的内存失败,内存大小:%d."), dwFileSize);
        WriteLog(strLog);
        CloseHandle(handle);
        return;
    }

    // 将图片文件中的内容读到buffer中
    if (!ReadFile(handle, pData, dwFileSize, &dwReadBytes, NULL))
    {
        WriteLog(_T("[CreateGrayHeadPic] 调用ReadFile失败,读取源图片文件失败."));

        CloseHandle(handle);
        free(pData);
        pData = NULL;
        return;
    }

    CloseHandle(handle);

    // * 描述 - 载入各种图片格式文件
    // * 函数 - stbi_load_from_memory()
    // 功能 - 加载图片
    // buffer - 内存数据
    // len - 数据大小,
    // x - [out]图像宽度(像素)
    // y - [out]图像高度(像素)
    // comp - [out]图像数据组成结果
    // req_comp - 图像数据组成
    // STBI_default = 0, // only used for req_comp
    // STBI_grey    = 1,
    // STBI_grey_alpha = 2,
    // STBI_rgb    = 3,
    // STBI_rgb_alpha = 4
    LPBYTE pImage = NULL;
    int n = 0;
    int nWidth = 0;
    int nHeight = 0;
    pImage = stbi_load_from_memory(pData, dwFileSize, &nWidth, &nHeight, &n, 4);
    if (!pImage)
    {
        const char* pFailReason = stbi_failure_reason();
        strLog.Format(_T("[CreateGrayHeadPic] 调用stbi_load_from_memory失败,原因:%s."),
            CopyCharToCStringT(pFailReason));
        WriteLog(strLog);

        CloseHandle(handle);
        free(pData);
        return;
    }

    free(pData);

    LPBYTE pDest = (LPBYTE)malloc(nWidth * nHeight * 4);
    if (NULL == pDest)
    {
        strLog.Format(_T("[CreateGrayHeadPic] 申请pDest内存失败,内存大小:%d."),
            nWidth * nHeight * 4);
        WriteLog(strLog);
        return;
    }

    for (int i = 0; i < nWidth * nHeight; i++)
    {
        pDest[i * 4 + 3] = pImage[i * 4 + 3];
        if (pDest[i * 4 + 3] < 255)
        {
            // 先将RGB值预乘alpha通道值
            pDest[i * 4] = (BYTE)(DWORD(pImage[i * 4])*pImage[i * 4 + 3] / 255) * 0.5;
            pDest[i * 4 + 1] = (BYTE)(DWORD(pImage[i * 4 + 1])*pImage[i * 4 + 3] / 255) * 0.5;
            pDest[i * 4 + 2] = (BYTE)(DWORD(pImage[i * 4 + 2])*pImage[i * 4 + 3] / 255) * 0.5;
        }
        else
        {
            pDest[i * 4] = pImage[i * 4] * 0.5;
            pDest[i * 4 + 1] = pImage[i * 4 + 1] * 0.5;
            pDest[i * 4 + 2] = pImage[i * 4 + 2] * 0.5;
        }
    }

    // stbi_write_png 参数说明:
    // filename: 输出文件名(标准字符串)
    // w : 图像宽度(像素)
    // h : 图像高度(像素)
    // comp : 颜色通道数(1 - 灰度, 3 - RGB, 4 - RGBA)
    // data : 图像数据指针
    // stride_in_bytes : 每行字节数(通常为宽度 * 通道数)
    s8 achGrayFilePath[512] = { 0 };
    CopyCStringTToChar(achGrayFilePath, strGrayFilePath, sizeof(achGrayFilePath));

    // png图片有透明通道,如果原图是png,则需要调用stbi_write_png接口;如果原图是其他类型,
    // 则统一转成jpg,调用stbi_write_jpg
    int nRet = 0;
    if (strGrayFilePath.Right(4) == _T(".png"))
    {
        nRet = stbi_write_png(achGrayFilePath, nWidth, nHeight, 4, pDest, nWidth * 4);
    }
    else
    {
        nRet = stbi_write_jpg(achGrayFilePath, nWidth, nHeight, 4, pDest, 90);
    }

    if (nRet == 0)
    {
        strLog.Format(_T("[CreateGrayHeadPic] 调用stbi_write_png或stbi_write_jpg失败,灰化图片文件路径:%s."),
            strGrayFilePath);
        WriteLog(strLog);
    }

    free(pDest);
    stbi_image_free(pImage);
}

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐