1. 指定链接库
源代码中直接指定链接库,用于告诉编译器在编译时需要链接特定的库文件 gdiplus.lib
。
#include <windows.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib") // 导入 GDI+ 库
2. 具体流程
启动 GDI+:在使用任何 GDI+ 功能之前,调用 Gdiplus::GdiplusStartup
进行初始化。通常可以在程序的初始化阶段或使用 GDI+ 的函数前调用。例如:
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
使用 GDI+ 功能:初始化之后,就可以使用 GDI+ 的各种功能了,比如 Gdiplus::Bitmap
等。
关闭 GDI+:在程序结束或不再需要 GDI+ 时,调用 Gdiplus::GdiplusShutdown
来清理和释放资源。清理代码一般放在程序的结束位置,例如:
Gdiplus::GdiplusShutdown(gdiplusToken);
3. 获取编码器
在 GDI+ 中,将图像保存为特定格式(如 PNG、JPEG、BMP)时,必须使用对应的编码器,而编码器是通过 CLSID(类标识符)来标识的。为了告诉 GDI+ 我们希望使用哪种格式保存图像,需要找到对应格式的 CLSID,并传递给 Gdiplus::Bitmap::Save
函数。
然而,CLSID 并不能直接从格式名称或文件扩展名推断出来,而需要通过查询系统中安装的编码器列表获取。因此,可以编写一个辅助函数 GetEncoderClsid
,根据 MIME 类型(如 image/png
)来获取对应编码器的 CLSID。
// 获取编码器 CLSID 的辅助函数
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
UINT num = 0; // 编码器数量
UINT size = 0; // 编码器信息结构体数组的大小
// 第一次调用获取编码器的数量和所需内存大小
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0) return -1; // 如果没有可用的编码器,返回 -1 表示失败
// 分配足够的内存来存储编码器信息
std::unique_ptr<BYTE[]> pImageCodecInfo(new BYTE[size]);
Gdiplus::ImageCodecInfo* pInfo = (Gdiplus::ImageCodecInfo*)pImageCodecInfo.get();
// 使用下面的函数也可以
//Gdiplus::ImageCodecInfo* pInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
// 获取所有编码器信息
Gdiplus::GetImageEncoders(num, size, pInfo);
// 遍历编码器信息,找到匹配的编码器
for (UINT j = 0; j < num; ++j) {
if (wcscmp(pInfo[j].MimeType, format) == 0) { // 判断 MIME 类型是否匹配
*pClsid = pInfo[j].Clsid; // 复制匹配的编码器 CLSID
return j; // 返回编码器索引(找到则返回)
}
}
return -1; // 没有找到匹配的编码器时返回 -1
}
4. 保存 .png 图像
bool EnsureDirectoryExists(const std::string& directoryPath)
{
// 检查是否是根目录
if (directoryPath.length() == 2 && isalpha(directoryPath[0]) && directoryPath[1] == ':') {
return true; // 根目录无需创建
}
// 使用 Windows API 创建目录
#ifdef _WIN32
if (CreateDirectoryA(directoryPath.c_str(), nullptr) || GetLastError() == ERROR_ALREADY_EXISTS) {
return true; // 目录创建成功或已经存在
}
else {
std::cerr << "Error creating directory: " << GetLastError() << std::endl;
return false; // 创建失败
}
#else
// 使用 POSIX 方法检查和创建目录
struct stat info;
if (stat(directoryPath.c_str(), &info) != 0) {
// 目录不存在,尝试创建
if (mkdir(directoryPath.c_str(), 0755) == 0) {
return true; // 目录创建成功
}
else {
std::cerr << "Error creating directory: " << strerror(errno) << std::endl;
return false; // 创建失败
}
}
else if (info.st_mode & S_IFDIR) {
return true; // 目录已存在
}
else {
std::cerr << "Path exists but is not a directory." << std::endl;
return false; // 不是目录
}
#endif
}
void SaveImageToFile(const std::vector<char>& imageData, const std::string& filePath, int width, int height, int channels) {
// 目录处理
std::size_t pos = filePath.find_last_of("/\\");
std::string directoryPath = filePath.substr(0, pos);
if (!EnsureDirectoryExists(directoryPath)) {
std::cout << "Failed to create directory for saving file" << std::endl;
return;
}
// 图像处理
std::unique_ptr<Gdiplus::Bitmap> bitmap;
Gdiplus::BitmapData bitmapData;
Gdiplus::Rect rect(0, 0, width, height);
if (channels == 1) {
bitmap = std::make_unique<Gdiplus::Bitmap>(width, height, PixelFormat32bppARGB);
if (bitmap->LockBits(&rect, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &bitmapData) == Gdiplus::Ok) {
BYTE* pDest = static_cast<BYTE*>(bitmapData.Scan0);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
BYTE gray = static_cast<BYTE>(imageData[y * width + x]);
int index = (y * bitmapData.Stride) + (x * 4);
pDest[index + 0] = gray; // B
pDest[index + 1] = gray; // G
pDest[index + 2] = gray; // R
pDest[index + 3] = 255; // A
}
}
bitmap->UnlockBits(&bitmapData);
}
} else if (channels == 3) {
bitmap = std::make_unique<Gdiplus::Bitmap>(width, height, PixelFormat24bppRGB);
if (bitmap->LockBits(&rect, Gdiplus::ImageLockModeWrite, PixelFormat24bppRGB, &bitmapData) == Gdiplus::Ok) {
BYTE* pDest = static_cast<BYTE*>(bitmapData.Scan0);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int index = (y * bitmapData.Stride) + (x * 3);
pDest[index + 0] = static_cast<BYTE>(imageData[(y * width + x) * 3 + 0]); // B
pDest[index + 1] = static_cast<BYTE>(imageData[(y * width + x) * 3 + 1]); // G
pDest[index + 2] = static_cast<BYTE>(imageData[(y * width + x) * 3 + 2]); // R
}
}
bitmap->UnlockBits(&bitmapData);
}
} else if (channels == 4) {
bitmap = std::make_unique<Gdiplus::Bitmap>(width, height, PixelFormat32bppARGB);
if (bitmap->LockBits(&rect, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &bitmapData) == Gdiplus::Ok) {
BYTE* pDest = static_cast<BYTE*>(bitmapData.Scan0);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int index = (y * bitmapData.Stride) + (x * 4);
pDest[index + 0] = static_cast<BYTE>(imageData[(y * width + x) * 4 + 0]); // B
pDest[index + 1] = static_cast<BYTE>(imageData[(y * width + x) * 4 + 1]); // G
pDest[index + 2] = static_cast<BYTE>(imageData[(y * width + x) * 4 + 2]); // R
pDest[index + 3] = static_cast<BYTE>(imageData[(y * width + x) * 4 + 3]); // A
}
}
bitmap->UnlockBits(&bitmapData);
}
}
// 获取编码器并保存
CLSID clsid;
if (GetEncoderClsid(L"image/png", &clsid) != -1) {
Gdiplus::Status status = bitmap->Save(std::wstring(filePath.begin(), filePath.end()).c_str(), &clsid, nullptr);
if (status != Gdiplus::Ok) {
// 输出错误信息到控制台
switch (status) {
case Gdiplus::OutOfMemory:
std::cout << "Out of memory" << std::endl;
break;
case Gdiplus::InvalidParameter:
std::cout << "Invalid parameter" << std::endl;
break;
case Gdiplus::ObjectBusy:
std::cout << "Object is busy" << std::endl;
break;
case Gdiplus::InsufficientBuffer:
std::cout << "Insufficient buffer" << std::endl;
break;
default:
std::cout << "Unknown error" << std::endl;
break;
}
}
} else {
std::cout << "Failed to get encoder CLSID" << std::endl;
}
}
5. CStatic 上显示图像
void DisplayImage(const std::vector<char>& imageData, int width, int height, int channels)
{
if (!running || width <= 0 || height <= 0) return;
// 将 std::vector<char> 转换为 unsigned char*
const unsigned char* pPixels = reinterpret_cast<const unsigned char*>(imageData.data());
// 设置 BITMAPINFO 结构
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(bmi));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = -height; // 如果高度为负数,则图像从上到下绘制
bmi.bmiHeader.biPlanes = 1;
// 根据通道数设置位深
if (channels == 1) {
// 单通道,灰度图像
bmi.bmiHeader.biBitCount = 8; // 每像素8位,灰度图像
bmi.bmiHeader.biClrUsed = 256; // 颜色表的条目数为256
bmi.bmiHeader.biClrImportant = 256;
// 设置颜色表(灰度调色板)
RGBQUAD palette[256];
for (int i = 0; i < 256; ++i) {
palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
palette[i].rgbReserved = 0;
}
memcpy(bmi.bmiColors, palette, sizeof(palette)); // 将颜色表复制到 bmi 中
}
else if (channels == 3) {
// 三通道,RGB图像
bmi.bmiHeader.biBitCount = 24; // 每像素24位,RGB图像
}
else if (channels == 4) {
// 四通道,RGBA图像
bmi.bmiHeader.biBitCount = 32; // 每像素32位,RGBA图像
}
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
// 获取控件区域
CRect rect;
m_strImageControl.GetClientRect(&rect);
// 获取控件的 DC
CDC* pDC = m_strImageControl.GetDC();
// 使用 StretchDIBits 绘制图像
::StretchDIBits(
pDC->GetSafeHdc(),
0, 0, rect.Width(), rect.Height(), // 目标矩形区域
0, 0, width, height, // 源图像大小
pPixels, // 像素数据
&bmi, // 位图信息
DIB_RGB_COLORS, // 颜色表使用 RGB 颜色
SRCCOPY // 绘制模式
);
// 释放 DC
m_strImageControl.ReleaseDC(pDC);
}
6. 获取图像文件的像素值
bool GetImagePixelData(LPCTSTR imagePath, std::vector<unsigned char>& pixels, UINT& width, UINT& height, int& channel)
{
Gdiplus::Bitmap* bitmap = Gdiplus::Bitmap::FromFile(imagePath);
if (bitmap == nullptr) {
return false; // 检查空指针
}
width = bitmap->GetWidth();
height = bitmap->GetHeight();
Gdiplus::PixelFormat pixelFormat = bitmap->GetPixelFormat();
// 确定通道数
if (pixelFormat == PixelFormat24bppRGB)
channel = 3;
else if (pixelFormat == PixelFormat32bppARGB || pixelFormat == PixelFormat32bppRGB)
channel = 4;
else if (pixelFormat == PixelFormat8bppIndexed)
channel = 1;
else {
delete bitmap; // 释放资源
return false; // 不支持的格式
}
pixels.resize(width * height * channel);
Gdiplus::BitmapData bitmapData;
Gdiplus::Rect rect(0, 0, width, height);
if (bitmap->LockBits(&rect, Gdiplus::ImageLockModeRead, pixelFormat, &bitmapData) == Gdiplus::Ok)
{
unsigned char* src = static_cast<unsigned char*>(bitmapData.Scan0);
int stride = bitmapData.Stride;
// 使用 std::copy 拷贝数据
for (UINT y = 0; y < height; y++)
{
std::copy(src + y * stride, src + y * stride + width * channel, &pixels[y * width * channel]);
}
bitmap->UnlockBits(&bitmapData);
}
delete bitmap; // 释放资源
return true;
}
特殊说明:
a. 在使用 GDI+ 的库之前,必须执行 Gdiplus::GdiplusStartup
进行初始化。在程序结束或不再需要 GDI+ 时,也应调用 Gdiplus::GdiplusShutdown
来释放资源。这是因为 GDI+ 需要进行全局初始化来管理资源和设置,然后在完成后进行清理。
b. 尽量在程序中只调用一次。重复调用可能导致意外行为。
c. 比特是图像和字符的基础,所有信息最终都以比特的形式存储。
d. 图像是由多个像素(Pixel)组成的,每个像素代表图像中的一个点。图像的质量和细节通常与像素的数量和颜色深度有关。
e. 每个像素可以使用多个比特来表示其颜色。例如,24 位图像(PixelFormat24bppRGB)使用 3 个字节(即 24 个比特)表示每个像素的颜色(红、绿、蓝)。
f. 使用 #ifdef _DEBUG #define new DEBUG_NEW #endif
是为了在调试模式下重定义 new
运算符,以帮助跟踪内存分配和泄漏。然而,在某些情况下,这个定义会导致内存分配错误,例如在创建 GDI+ 对象时,因为 DEBUG_NEW
的定义可能与 GDI+ 的内部机制不兼容。
示例代码:
#include <windows.h>
#include <gdiplus.h>
#include <vector>
#include <string>
#include <iostream>
#pragma comment(lib, "gdiplus.lib") // 导入 GDI+ 库
// GDI+ 初始化和清理封装
class GdiplusInitializer {
public:
GdiplusInitializer() {
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
}
~GdiplusInitializer() {
Gdiplus::GdiplusShutdown(gdiplusToken);
}
private:
ULONG_PTR gdiplusToken;
};
// 使用 GDI+ 将像素数据保存为 PNG 文件
bool SaveImageUsingGdiplus(const std::vector<char>& imageData, int width, int height, const std::wstring& filename) {
Gdiplus::Bitmap bitmap(width, height, PixelFormat32bppARGB);
// 锁定位图的内存区域,并填充像素数据
Gdiplus::BitmapData bitmapData;
Gdiplus::Rect rect(0, 0, width, height);
if (bitmap.LockBits(&rect, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &bitmapData) == Gdiplus::Ok) {
// 假设 imageData 是 32 位 ARGB 格式的像素数据
std::memcpy(bitmapData.Scan0, imageData.data(), imageData.size());
bitmap.UnlockBits(&bitmapData);
} else {
std::cerr << "Failed to lock bits for the bitmap." << std::endl;
return false;
}
// 获取 PNG 编码器的 CLSID
CLSID pngClsid;
Gdiplus::GetEncoderClsid(L"image/png", &pngClsid);
// 保存图像到文件
if (bitmap.Save(filename.c_str(), &pngClsid, nullptr) != Gdiplus::Ok) {
std::cerr << "Failed to save the image." << std::endl;
return false;
}
return true;
}
// 获取编码器 CLSID 的辅助函数
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
UINT num = 0; // 编码器数量
UINT size = 0; // 编码器大小
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0) return -1; // 无编码器
std::unique_ptr<BYTE[]> pImageCodecInfo(new BYTE[size]);
Gdiplus::ImageCodecInfo* pInfo = (Gdiplus::ImageCodecInfo*)pImageCodecInfo.get();
Gdiplus::GetImageEncoders(num, size, pInfo);
for (UINT j = 0; j < num; ++j) {
if (wcscmp(pInfo[j].MimeType, format) == 0) {
*pClsid = pInfo[j].Clsid;
return j;
}
}
return -1;
}
int main() {
GdiplusInitializer gdiplusInit; // 初始化 GDI+
// 示例像素数据:假设为 32 位 ARGB 格式,每个像素 4 字节
int width = 100;
int height = 100;
std::vector<char> imageData(width * height * 4, (char)255); // 简单示例数据,填充为白色
// 保存图像
if (SaveImageUsingGdiplus(imageData, width, height, L"output.png")) {
std::cout << "Image saved successfully!" << std::endl;
} else {
std::cout << "Failed to save image." << std::endl;
}
return 0; // 程序结束时会自动调用 GDI+ 清理
}