从零开始学习GDI+ (一)我的第一个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+ (二) 基本概念与基本操作的更多相关文章

  1. 从零开始学习jQuery (二) 万能的选择器

    本系列文章导航 从零开始学习jQuery (二) 万能的选择器 一.摘要 本章讲解jQuery最重要的选择器部分的知识. 有了jQuery的选择器我们几乎可以获取页面上任意的一个或一组对象, 可以明显 ...

  2. 从零开始学习GDI+ (一)

    前言: GDI+从Windows XP操作系统(大概2002-2003年)开始引入的,现在都9102年了,再学习这么古老的技术肯定是过时了.windows桌面程序没落了,随着移动的兴起,用户被惯坏了, ...

  3. 从零开始学习Android(二)从架构开始说起

    我们刚开始学新东西的时候,往往希望能从一个实例进行入手学习.接下来的系列连载文章也主要是围绕这个实例进行.这个实例原形是从电子书<Android应用开发详解>得到的,我们在这里对其进行详细 ...

  4. 从零开始学JavaScript二(基本概念)

    基本概念 一.区分大小写 在ECMAScript中的一切(变量.函数名.操作符)都是区分大小写的. 如变量名test和Test分别表示两个不同的变量, 二.标识符 所谓标识符,就是指变量.函数.属性的 ...

  5. apache Storm学习之二-基本概念介绍

    2.1 Storm基本概念 在运行一个Storm任务之前,需要了解一些概念: Topologies Streams Spouts Bolts Stream groupings Reliability ...

  6. 从零开始学习Vue(二)

    思维方式的变化 WebForm时代, Aspx.cs 取得数据,绑定到前台的Repeater之类的控件.重新渲染整个HTML页面.就是整个页面不断的刷新;后来微软打了个补丁,推出了AJAX控件,比如U ...

  7. oracle从零开始学习笔记 二

    多表查询 等值连接(Equijoin) select ename,empno,sal,emp.deptno from emp,dept where dept.deptno=emp.deptno; 非等 ...

  8. 从零开始学习GDI+ (三) 画笔与画刷

  9. 从零开始学习SQL SERVER(2)--- 基本操作及语句

    声明:仅为本人随笔及经验之谈,有错误敬请指出. # 后的文字为注释 Microsoft SQL Server Management Studio 中的SQL命令 添加数据库 1 CREATE DATA ...

随机推荐

  1. qt5-QPushButton按钮

    Win::Win(QWidget *parent) //构造函数体 : QWidget(parent) //执行父类初始化操作 { //创建按钮方式一 ,);//重置窗口大小 this->set ...

  2. mysql 8.0.18 手工安装记录

    mysql 8.0.18 手工安装记录 为了日常方便,特记录如下. 一.安装系统依赖包 #.系统依赖包安装 yum -y install make gcc-c++ cmake bison-devel ...

  3. 【MongoDB系列】简介、安装、基本操作命令

    文章内容概述: 1.MongoDB介绍 2.MongoDB安装(windows及Linux) 3.MongoDB基本操作命令 MongoDB介绍: MongoDB 是一个基于分布式文件存储的数据库.由 ...

  4. Flask之请求上下文流程图

    整理一下Flask请求上下文流程导思流程图,如果错误,请指出.

  5. 修改 Linux 服务器时间

    1.当前时间 [app@127-0-0-1 shine]$ date Wed Oct 23 11:44:30 CST 2019 2.修改时间 [app@127-0-0-1 shine]$ date - ...

  6. T级别视频上传解决方案

    之前仿造uploadify写了一个HTML5版的文件上传插件,没看过的朋友可以点此先看一下~得到了不少朋友的好评,我自己也用在了项目中,不论是用户头像上传,还是各种媒体文件的上传,以及各种个性的业务需 ...

  7. Visual Studio 2008:路径设置

    造冰箱的大熊猫,本文适用于Visual Studio 2008中文版@cnblogs 2018/11/30 1.头文件路径设置 如果头文件所在路径未在环境变量中定义,编译时会出现C1083错误,提示无 ...

  8. Nowcoder 北师校赛 B 外挂使用拒绝 ( k次前缀和、矩阵快速幂打表找规律、组合数 )

    题目链接 题意 : 中文题.点链接 分析 : 有道题是问你不断求前缀和后的结果 Click here 这道题问的是逆过程 分析方法雷同.可参考 Click here ----------------- ...

  9. CodeForces451E Devu and Flowers

    题目链接 问题分析 没有想到母函数的做法-- 其实直接看题思路挺简单的.发现如果每种花都有无限多的话,问题变得十分简单,答案就是\(s+n-1\choose n - 1\).然后发现\(n\)只有\( ...

  10. HDU4587--TWO NODES(无向图割点,暴力出奇迹)这是我见过的时间最长的题。。。

    TWO NODES Time Limit: 24000/12000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total ...