使用 GDI+ 保存图像文件

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+ 清理
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值