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);}// 保存为PNGbitmap.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();// 保存为PNGCLSID 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);// 保存为PNGCLSID 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+接口均需手动声明,开发复杂度较高