从零开始学习GDI+ (二) 基本概念与基本操作
上文给新手学习GDI+讲述了vs环境等的准备工作,并且可以直接用GDI+绘图了。本文开始,讲述的可能偏理论,建议学习的过程中大胆尝试,多使用API。
首先上官方文档https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-gdi-start
官方文档是最权威与第一手(当然有时候有错误)的,其他人的说法经过自己的加工,增加了解释,也会带来错误的风险。英文能力强,强烈建议通过官网
学习与尝试。
GDI+的新特性。
1、图像(Graphics)对象 与画图工具(如Pen、Brush、GraphicsPath、Font、Image)分离,这与GDI中需要将画图工具导入DC中完全不同。因此GDI也
称为状态模型编程,而GDI+则称为非状态模型编程。松耦合总是易于方便拓展。在我们这个例子中就是绘图更自由了,调整画笔时,不再需要频繁取出dc与存入dc了。
2、多函数重载。以DrawLine为例,可以传入Point,也可以传入int,方便不同场景使用不同的API。而GDI则比较固定。
3、当前位置,GDI讲究当前的绘图点,模拟一个人画画的全过程。如画一条线的话,需要调用MoveToEx(该函数甚至返回之前的点),再调用LineTo
而GDI+则无当前位置的概念,讲究的是绘制过程。DrawLine传入的起始点与结束点便可以划线。
4、绘制与填充,GDI中Rectangle(矩形)直接使用dc的画笔画边界(border),用dc的画刷填充。而GDI+则分成了两个部分DrawRectangle与FillRectangle。
5、区域的操作,GDI提供的区域函数比较简单 CreateRoundRectRgn、CreatePolygonRgn、CreateEllipseRgn等。GDI+不提供类似函数,通过Region维护,并提供了
Intersect,Union,Xor,Exclude,Complement,Translate等功能函数来构建复杂地区域。
GDI+的使用:
我们一般在main函数入口附近进行初始化GdiplusStartup ,并在程序结束前进行资源回收GdiplusShutdown。如果不进行初始化,任何使用GDI+编译不会报错,甚至也能运行,
但如你所见,UI全是空的,因为GDI+对象无法正常工作。
GDI+的基本操作:
1、重要的Graphics对象。
Graphics是GDI+的核心,他的构造函数的参数解释下。
(绘图本质是利用绘图工具在绘图平面上做画,以下我把绘图平面称为【画布】)
hdevice:设备句柄,此时画布如打印机
hwnd:窗口句柄,此时画布是窗口
image:图像对象,此时画布是图片(没错,图片也可以作画,画完后保存的话图片变了)
hdc:设备上下文句柄,此时画布要根据上下文才能知道
icm:是否使用色彩配置文件校正色彩。
总之,Graphics可以在应用程序窗口、图片、打印机、绘图仪、传真机等等作画!
动手试一试吧:
1)写一个在打印机作画的函数 注意需要 #include <commdlg.h>
void OnPrintOut()
{
//要打印的文档信息
DOCINFO docInfo;
ZeroMemory(&docInfo, sizeof(docInfo));
docInfo.cbSize = sizeof(docInfo); docInfo.lpszDocName = _T("TestPrint"); PRINTDLG printDlg;
ZeroMemory(&printDlg, sizeof(printDlg));
printDlg.lStructSize = sizeof(printDlg);
printDlg.Flags = PD_RETURNDC; if (PrintDlg(&printDlg))
{
StartDoc(printDlg.hDC, &docInfo);
StartPage(printDlg.hDC); //开始在打印机上作画
Graphics graphics(printDlg.hDC); //调试的话放到cpp目录,否则放到exe目录
Image image(_T("test.png"));
graphics.DrawImage(&image, , ); Pen blue(Color(, , , ));
graphics.DrawRectangle(&blue, , , , );
graphics.DrawEllipse(&blue, , , , );
EndPage(printDlg.hDC);
EndDoc(printDlg.hDC);
} if (printDlg.hDevMode)
{
GlobalFree(printDlg.hDevMode);
}
if (printDlg.hDevNames)
{
GlobalFree(printDlg.hDevNames);
}
if (printDlg.hDC)
{
DeleteDC(printDlg.hDC);
}
}
2)在菜单功能,把“关于”菜单项的功能注释掉,改成我们的功能
3)编译运行看看,如果有打印机,看看打印出来的是否是你画的呢?
2、画基本图形
GDI+的默认的坐标系的原点位于画布的左上角,x轴向右,y轴向下。默认的单位是像素。(让我想起了高DPI的恐慌,目前网易云信也是不支持动态变化的,但重启会生效。还得抽时间攻克下。)注意:不同的画布的像素的大小不一定一样,更改了分辨率也会影响像素。比如从480*720 变到920*1440,明显程序变小了。
1)画直线
一条直线先前已经玩过了,这里补充下一次画多根线。
void GDIPlusDrawLines(HDC hdc)
{
Graphics graphics(hdc);
Pen green(Color(, , , ), );
PointF p1(, );
PointF p2(, );
PointF p3(, );
PointF p4(, );
PointF point[] = { p1, p2, p3, p4 };
graphics.DrawLines(&green, point, sizeof(point) / sizeof(point[])); }
2、画矩形,之前也玩过了,GDI+支持一次性画多个矩形,试试吧。
void GDIPlusDrawRectangles(HDC hdc)
{
Graphics graphics(hdc);
Pen blue(Color(, , , ), );
RectF r1(,,,);
RectF r2(,,,);
RectF r3(,,,);
RectF rs[] = { r1, r2, r3};
graphics.DrawRectangles(&blue, rs, sizeof(rs) / sizeof(rs[])); }
3、画曲线
DrawCurve、DrawClosedCurve(闭合曲线)、DrawBezier(贝塞尔曲线)
额,发现每个函数都写demo比较费时且无聊,大家又不一定跟着尝试,增加点乐趣吧,点的位置随机,矩阵的位置随机。
1) 包含下头文件 <time.h> 和<stdlib.h>,using namespace std;
2) 初始化指定下随机数种子
//初始化种子
srand((unsigned)time(NULL));
3)写随机函数
Point GetRandomPoint(int xmax, int ymax)
{
Point t;
t.X = rand() % xmax;
t.Y = rand() % ymax;
return t;
} Rect GetRandomRect(int xmax, int ymax)
{
Rect t;
Point t1 = GetRandomPoint(xmax, ymax);
Point t2 = GetRandomPoint(xmax, ymax); if (t1.X < t2.X)
{
t.X = t1.X;
t.Width = t2.X - t1.X;
if (t1.Y<t2.Y)
{
t.Y = t1.Y;
t.Height = t2.Y - t1.Y;
}
else
{
t.Y = t2.Y;
t.Height = t1.Y - t2.Y;
}
}
else
{
t.X = t2.X;
t.Width = t1.X - t2.X;
if (t1.Y < t2.Y)
{
t.Y = t1.Y;
t.Height = t2.Y - t1.Y;
}
else
{
t.Y = t2.Y;
t.Height = t1.Y - t2.Y;
}
} return t;
}
4)描点函数
//描点
void DrawEllipsePoint(HDC hdc, Point t)
{
Graphics graphics(hdc);
SolidBrush redbursh(Color::Red);
graphics.FillEllipse(&redbursh, t.X-, t.Y-, , );
}
5)开始作画
void DrawCurves(HDC hdc, int xmax, int ymax)
{
Graphics graphics(hdc);
Point t[] = { GetRandomPoint(xmax, ymax),
GetRandomPoint(xmax, ymax), GetRandomPoint(xmax, ymax), GetRandomPoint(xmax, ymax) }; int t_size = sizeof(t) / sizeof(t[]);
//画曲线
Pen green(Color::Green, );
graphics.DrawCurve(&green, t, t_size);
//增加弯曲程度
Pen blue(Color::Blue, );
graphics.DrawCurve(&blue, t, t_size, 1.3f);
//画闭合曲线
Pen gray(Color::Gray, );
graphics.DrawClosedCurve(&gray, t, t_size);
//画贝塞尔曲线
Pen orange(Color::Orange, );
graphics.DrawBezier(&orange, t[],t[],t[],t[]); for (int i = ; i < t_size;++i)
{
DrawEllipsePoint(hdc, t[i]);
}
}
4、画圆弧与扇形
DrawArc 、DrawPie
void DrawArcPie(HDC hdc,int xmax, int ymax)
{
Graphics graphics(hdc);
Rect t = GetRandomRect(xmax, ymax);
//先画矩形边框(增加对左边的认知)
Pen black(Color::Black); //默认一像素
graphics.DrawRectangle(&black, t);
//画弧线
Pen red(Color::Red, 3);
graphics.DrawArc(&red, t, 0/*起始位置*/, 90/*需要画的弧度大小*/);
//画扇形
Pen green(Color::Green, 1);
graphics.DrawPie(&green, t, 90/*起始位置*/, 90/*需要画的弧度大小*/);
}
5、填充区域、画刷与颜色
FillClosedCurve(填充封闭曲线)、FillEllipse(填充椭圆)、FillPath(填充路径)
FillPie(填充扇形)、FillPolygon(填充多边形)、FillRectangle(填充矩形)
FillRectangles(填充巨型集)、FillRegion(填充区域)。
GDI+使用画刷来填充的:单色画刷、影线画刷、纹理画刷、线性渐变画刷与路径渐变画刷(画刷以后详细展开)
我们之前已经接触过颜色了,Color,由argb组成。a是alpha色彩的透明度、r是red红色、g是green绿色、b是blue蓝色。GDI+对透明度的支持是基于以下算法:output = foreground*alpha/255 + background*(255-alpha)/255。(以后详细展开)
下面,我们来填充一个正弦图形试试
//用半透明蓝色填充 填充sinx 与 x轴的区域
void FillSinRegion(HDC hdc, int xmax, int ymax)
{
const REAL Pi = 3.1415926;
//从0 到 2pi
//计算1弧度=多少像素,从50px ->xmax-50px 画
REAL perX = (xmax - )*1.0 / ( * Pi);
//众项长度单位1=多少像素
REAL perY = (ymax - )*1.0/; //画点,理论上越多越精确,我们取500+1点。大家可以试试更多
const int counts = ;
Point t[counts*];
t[].X = ;
t[].Y = ymax / ;
//步长
REAL stepX = ( * Pi) / counts;
REAL step = stepX*perX ;
//计算正弦值
for (int i = ; i < counts ; i++)
{
t[i].X = t[i - ].X + step;
REAL v = sin(i*stepX);
t[i].Y = ymax / - v*perY;
}
//画x轴
t[counts].X = t[counts - ].X;
t[counts].Y = ymax / ;
for (int i = ; i < counts; i++)
{
t[counts + i].X = t[counts + i - ].X - step;
t[counts + i].Y = ymax / ;
} Graphics graphics(hdc);
SolidBrush blue(Color( / , , , ));
Pen r(Color::Red);
graphics.DrawPolygon(&r, t, counts*);
graphics.FillClosedCurve(&blue,t,counts*); }
6、输出问题
DrawString
void PrintText(HDC hdc, int xmax, int ymax)
{
TCHAR s[];
_tcscpy_s(s, _T("Hello GDI+")); RectF t(,,,); Font f(_T("Arial"), );
StringFormat Fmt;
Fmt.SetAlignment(StringAlignmentCenter);
Fmt.SetLineAlignment(StringAlignmentCenter); SolidBrush red(Color::Red);
Graphics graphics(hdc);
graphics.DrawString(s, _tcslen(s), &f, t, &Fmt, &red); Pen black(Color::Black);
graphics.DrawRectangle(&black, t); }
本文所有源码见:https://github.com/xuhuajie-NetEase/GDI-Study
从零开始学习GDI+ (二) 基本概念与基本操作的更多相关文章
- 从零开始学习jQuery (二) 万能的选择器
本系列文章导航 从零开始学习jQuery (二) 万能的选择器 一.摘要 本章讲解jQuery最重要的选择器部分的知识. 有了jQuery的选择器我们几乎可以获取页面上任意的一个或一组对象, 可以明显 ...
- 从零开始学习GDI+ (一)
前言: GDI+从Windows XP操作系统(大概2002-2003年)开始引入的,现在都9102年了,再学习这么古老的技术肯定是过时了.windows桌面程序没落了,随着移动的兴起,用户被惯坏了, ...
- 从零开始学习Android(二)从架构开始说起
我们刚开始学新东西的时候,往往希望能从一个实例进行入手学习.接下来的系列连载文章也主要是围绕这个实例进行.这个实例原形是从电子书<Android应用开发详解>得到的,我们在这里对其进行详细 ...
- 从零开始学JavaScript二(基本概念)
基本概念 一.区分大小写 在ECMAScript中的一切(变量.函数名.操作符)都是区分大小写的. 如变量名test和Test分别表示两个不同的变量, 二.标识符 所谓标识符,就是指变量.函数.属性的 ...
- apache Storm学习之二-基本概念介绍
2.1 Storm基本概念 在运行一个Storm任务之前,需要了解一些概念: Topologies Streams Spouts Bolts Stream groupings Reliability ...
- 从零开始学习Vue(二)
思维方式的变化 WebForm时代, Aspx.cs 取得数据,绑定到前台的Repeater之类的控件.重新渲染整个HTML页面.就是整个页面不断的刷新;后来微软打了个补丁,推出了AJAX控件,比如U ...
- oracle从零开始学习笔记 二
多表查询 等值连接(Equijoin) select ename,empno,sal,emp.deptno from emp,dept where dept.deptno=emp.deptno; 非等 ...
- 从零开始学习GDI+ (三) 画笔与画刷
- 从零开始学习SQL SERVER(2)--- 基本操作及语句
声明:仅为本人随笔及经验之谈,有错误敬请指出. # 后的文字为注释 Microsoft SQL Server Management Studio 中的SQL命令 添加数据库 1 CREATE DATA ...
随机推荐
- 10.17小作业 基于TCP开发一款远程CMD程序
基于TCP开发一款远程CMD程序 客户端连接服务器后,可以向服务器发送命令 服务器收到命令后执行,无论执行是否成功,无论执行几遍,都将执行结果返回给客户端 注意: 执行系统指令使用subprocess ...
- 自定义 Swiper 的pageControl
.part5-bg .swiper2 .swiper-pagination2{ bottom: 0.4rem; } /*未选中的小圆点样式*/ .part5-bg .swiper2 .swiper-p ...
- Flask之请求上下文流程图
整理一下Flask请求上下文流程导思流程图,如果错误,请指出.
- QT:QSS ID选择器无效
我正在学习使用Qt样式表给我的应用程序添加不同的样式.我上网看了看Qt文档,上面说你可以使用一种ID选择器,它可以把主题应用到某些对象上.我就是这样实现这个特性的: QPushButton#butto ...
- C语言 - strcmp和strncmp的编程实现及总结
一.strcmp和strncmp的编程实现及总结 1.strcmp函数的实现 要求: 原型: int strcmp(char *dest,char * src,int n); 头文件:# ...
- [HG]腿部挂件 题解
前言 暴力跑的比正解快. 以下暴力(循环展开+fread读入输出优化) #include<cstdio> #pragma GCC optimize(3, "Ofast" ...
- (转载)rabbitmq与springboot的安装与集成
原文地址:https://segmentfault.com/a/1190000016991529 一.前言 RabbitMQ是一个开源的消息代理软件(面向消息的中间件),它的核心作用就是创建消息队列, ...
- 大哥带的XSS练习
0x01反射型 <script>alert("zhong")</script> 可以看见什么都没有过滤 0x02存储型XSS http://www.xss_ ...
- LinkedList类源码浅析(一)
1.先来看一看LinkedList类的字段和构造方法 size记录链表的长度,first永远指向链表的第一个元素,last永远指向链表的最后一个元素 提供两个构造方法,一个无参的构造方法,一个接受一个 ...
- zeppelin安装使用
官网:http://zeppelin-project.org/ 代码:https://github.com/NFLabs/zeppelin 使用:按照官网的视频操作一遍,应该就懂了http://y ...