C++与C#使用GDI+创建PNG并旋转文本的对比实现
·
以下是使用GDI+在C++和C#中创建PNG图片并写入旋转30度文本的实现对比:
| 特性 | C++实现 | C#实现 |
|---|---|---|
| 代码量 | 约50行 | 约15行 |
| 初始化 | 需要显式初始化GDI+环境 | 自动处理GDI+初始化 |
| 核心类 | Gdiplus::Bitmap, Gdiplus::Graphics |
System.Drawing.Bitmap, System.Drawing.Graphics |
| 旋转实现 | 使用Matrix类或RotateTransform |
直接使用RotateTransform方法 |
| 保存格式 | 需要获取PNG编码器CLSID | 直接指定保存格式为PNG |
| 资源释放 | 需要手动释放GDI+对象 | 可使用using语句自动释放 |
C#画图,只要9行代码,VC++要50行代码
VC++纯api调用gdiplus.dll要100行左右的代码
using System.Drawing;
using System.Drawing.Imaging;
class Program {
static void Main() {
// 创建位图
using(var bitmap = new Bitmap(400, 200)) {
using(var graphics = Graphics.FromImage(bitmap)) {
// 设置背景色
graphics.Clear(Color.White);
// 设置文本属性
var font = new Font("Arial", 24);
var brush = new SolidBrush(Color.Black);
// 旋转并绘制文本
graphics.TranslateTransform(100, 50);
graphics.RotateTransform(30);
graphics.DrawString("helloworld", font, brush, 0, 0);
}
// 保存为PNG
bitmap.Save("output.png", ImageFormat.Png);
}
}
}
关键差异分析
-
初始化过程:
- C++需要显式初始化和清理GDI+环境
- C#自动处理GDI+的初始化和资源管理
-
资源管理:
- C++需要手动释放所有GDI+对象
- C#可利用
using语句自动释放资源
-
图像保存:
- C++需要获取PNG编码器的CLSID
- C#直接使用
ImageFormat.Png枚举值
-
代码复杂度:
- C++实现需要更多样板代码
- C#语法更简洁,框架提供了更多便利方法
-
旋转实现:
- 两者都使用
RotateTransform方法 - C#的API设计更加直观易用
- 两者都使用
适用场景建议
- 选择C++:当需要最高性能、直接系统级访问或与现有C++代码库集成时
- 选择C#:当开发效率、代码简洁性和快速原型开发是首要考虑因素时
两种语言都能很好地完成GDI+图像处理任务,但C#的实现通常更为简洁高效,特别是在.NET环境下开发时。
VC++用GDIPLUS库画图,50行代码
#include <windows.h>
#include <gdiplus.h>
#include <iostream>
using namespace Gdiplus;
int main() {
// 初始化GDI+
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// 创建位图
Bitmap bitmap(400, 200, PixelFormat32bppARGB);
Graphics graphics(&bitmap);
// 设置背景色
graphics.Clear(Color(255, 255, 255, 255));
// 设置文本属性
Font font(L"Arial", 24);
SolidBrush brush(Color(255, 0, 0, 0));
PointF point(100.0f, 50.0f);
// 旋转文本30度
graphics.TranslateTransform(point.X, point.Y);
graphics.RotateTransform(30.0f);
graphics.DrawString(L"helloworld", -1, &font, PointF(0, 0), &brush);
graphics.ResetTransform();
// 保存为PNG
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
bitmap.Save(L"output.png", &pngClsid, NULL);
// 清理GDI+
GdiplusShutdown(gdiplusToken);
return 0;
}
// 获取编码器CLSID
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
UINT num = 0, size = 0;
GetImageEncodersSize(&num, &size);
if(size == 0) return -1;
ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)malloc(size);
GetImageEncoders(num, size, pImageCodecInfo);
for(UINT i = 0; i < num; i++) {
if(wcscmp(pImageCodecInfo[i].MimeType, format) == 0) {
*pClsid = pImageCodecInfo[i].Clsid;
free(pImageCodecInfo);
return i;
}
}
free(pImageCodecInfo);
return -1;
}
VC++纯api调用gdiplus.dll要100行左右的代码
#include <windows.h>
#include <objidl.h>
#include <stdio.h>
// 手动定义GDI+结构体
typedef struct {
UINT32 GdiplusVersion;
void* DebugEventCallback;
BOOL SuppressBackgroundThread;
BOOL SuppressExternalCodecs;
} GdiplusStartupInput;
// 声明函数指针类型
typedef int (WINAPI* GdiplusStartupFunc)(ULONG_PTR*, const GdiplusStartupInput*, void*);
typedef void (WINAPI* GdiplusShutdownFunc)(ULONG_PTR);
typedef int (WINAPI* GdipCreateBitmapFromScan0Func)(int, int, int, int, void*, void**);
typedef int (WINAPI* GdipGetImageGraphicsContextFunc)(void*, void**);
typedef int (WINAPI* GdipGraphicsClearFunc)(void*, DWORD);
typedef int (WINAPI* GdipCreateFontFunc)(void*, float, int, int, void**);
typedef int (WINAPI* GdipCreateSolidFillFunc)(DWORD, void**);
typedef int (WINAPI* GdipSetTextRenderingHintFunc)(void*, int);
typedef int (WINAPI* GdipDrawStringFunc)(void*, const WCHAR*, int, void*, void*, void*, void*);
typedef int (WINAPI* GdipSaveImageToFileFunc)(void*, const WCHAR*, void*, void*);
typedef int (WINAPI* GdipDeleteGraphicsFunc)(void*);
typedef int (WINAPI* GdipDeleteFontFunc)(void*);
typedef int (WINAPI* GdipDeleteBrushFunc)(void*);
typedef int (WINAPI* GdipDeleteBitmapFunc)(void*);
typedef int (WINAPI* GdipRotateWorldTransformFunc)(void*, float, int);
typedef int (WINAPI* GdipTranslateWorldTransformFunc)(void*, float, float, int);
int main() {
HMODULE hGdiplus = LoadLibrary(L"gdiplus.dll");
if (!hGdiplus) return 1;
// 获取函数地址
GdiplusStartupFunc GdiplusStartup = (GdiplusStartupFunc)GetProcAddress(hGdiplus, "GdiplusStartup");
GdiplusShutdownFunc GdiplusShutdown = (GdiplusShutdownFunc)GetProcAddress(hGdiplus, "GdiplusShutdown");
GdipCreateBitmapFromScan0Func GdipCreateBitmapFromScan0 = (GdipCreateBitmapFromScan0Func)GetProcAddress(hGdiplus, "GdipCreateBitmapFromScan0");
GdipGetImageGraphicsContextFunc GdipGetImageGraphicsContext = (GdipGetImageGraphicsContextFunc)GetProcAddress(hGdiplus, "GdipGetImageGraphicsContext");
GdipGraphicsClearFunc GdipGraphicsClear = (GdipGraphicsClearFunc)GetProcAddress(hGdiplus, "GdipGraphicsClear");
GdipCreateFontFunc GdipCreateFont = (GdipCreateFontFunc)GetProcAddress(hGdiplus, "GdipCreateFont");
GdipCreateSolidFillFunc GdipCreateSolidFill = (GdipCreateSolidFillFunc)GetProcAddress(hGdiplus, "GdipCreateSolidFill");
GdipSetTextRenderingHintFunc GdipSetTextRenderingHint = (GdipSetTextRenderingHintFunc)GetProcAddress(hGdiplus, "GdipSetTextRenderingHint");
GdipDrawStringFunc GdipDrawString = (GdipDrawStringFunc)GetProcAddress(hGdiplus, "GdipDrawString");
GdipSaveImageToFileFunc GdipSaveImageToFile = (GdipSaveImageToFileFunc)GetProcAddress(hGdiplus, "GdipSaveImageToFile");
GdipDeleteGraphicsFunc GdipDeleteGraphics = (GdipDeleteGraphicsFunc)GetProcAddress(hGdiplus, "GdipDeleteGraphics");
GdipDeleteFontFunc GdipDeleteFont = (GdipDeleteFontFunc)GetProcAddress(hGdiplus, "GdipDeleteFont");
GdipDeleteBrushFunc GdipDeleteBrush = (GdipDeleteBrushFunc)GetProcAddress(hGdiplus, "GdipDeleteBrush");
GdipDeleteBitmapFunc GdipDeleteBitmap = (GdipDeleteBitmapFunc)GetProcAddress(hGdiplus, "GdipDeleteBitmap");
GdipRotateWorldTransformFunc GdipRotateWorldTransform = (GdipRotateWorldTransformFunc)GetProcAddress(hGdiplus, "GdipRotateWorldTransform");
GdipTranslateWorldTransformFunc GdipTranslateWorldTransform = (GdipTranslateWorldTransformFunc)GetProcAddress(hGdiplus, "GdipTranslateWorldTransform");
// 初始化GDI+
GdiplusStartupInput gdiplusStartupInput = {1};
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// 创建位图(400x200 ARGB格式)
void* bitmap;
GdipCreateBitmapFromScan0(400, 200, 0, 0x26200A, NULL, &bitmap);
// 获取图形上下文
void* graphics;
GdipGetImageGraphicsContext(bitmap, &graphics);
// 设置白色背景
GdipGraphicsClear(graphics, 0xFFFFFFFF);
// 创建字体和画刷
void* font;
GdipCreateFont(L"Arial", 24, 0, 0, &font);
void* brush;
GdipCreateSolidFill(0xFF000000, &brush);
// 设置文本渲染质量
GdipSetTextRenderingHint(graphics, 4); // AntiAliasGridFit
// 旋转30度并绘制文本
GdipTranslateWorldTransform(graphics, 100, 50, 0);
GdipRotateWorldTransform(graphics, 30.0f, 0);
GdipDrawString(graphics, L"helloworld", -1, font, NULL, brush, NULL);
// 保存为PNG
CLSID pngClsid = {0x557cf406, 0x1a04, 0x11d3, {0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e}};
GdipSaveImageToFile(bitmap, L"output.png", &pngClsid, NULL);
// 释放资源
GdipDeleteGraphics(graphics);
GdipDeleteFont(font);
GdipDeleteBrush(brush);
GdipDeleteBitmap(bitmap);
GdiplusShutdown(gdiplusToken);
FreeLibrary(hGdiplus);
return 0;
}
以上是直接调用gdiplus.dll实现PNG创建与旋转文本的纯API方案,基于用户历史需求(C++实现)进行扩展:
核心实现原理
通过LoadLibrary/GetProcAddress动态加载gdiplus.dll,手动声明所有必需的函数原型和结构体,完全绕过GDI+头文件依赖。关键步骤包括:
- 动态加载gdiplus.dll并获取函数地址
- 手动定义GDI+所需的结构体(如GdiplusStartupInput)
- 通过函数指针调用GDI+接口
关键实现要点
- 动态加载机制:通过LoadLibrary/GetProcAddress实现运行时绑定,避免静态链接
- 结构体手动定义:完全重现GdiplusStartupInput等核心结构体
- PNG编码器CLSID:硬编码PNG编码器的类标识符(0x557cf406...)避免额外查询
- 资源管理:严格遵循创建/销毁配对原则,防止内存泄漏
- 坐标系变换:通过Translate/Rotate组合实现精确的文本旋转定位
对比原GDI+库方案
| 特性 | 纯API方案 | GDI+库方案 |
|---|---|---|
| 依赖项 | 仅需gdiplus.dll | 需要gdiplus.lib和头文件 |
| 初始化 | 手动定义所有结构体 | 自动包含预定义结构体 |
| 函数调用 | 通过函数指针间接调用 | 直接方法调用 |
| 代码量 | 显著增加(需声明所有接口) | 简洁 |
| 灵活性 | 可选择性加载所需函数 | 必须链接完整库 |
| 维护性 | 需自行跟踪接口变更 | 微软官方维护 |
该方案特别适用于需要严格控制依赖或进行GDI+功能裁剪的场景,但需注意所有GDI+接口均需手动声明,开发复杂度较高
更多推荐


所有评论(0)