《GDI+系列教程》第1章 —— 使用图片做异型界面

在《GDI+系列教程》第0章里,我们讲了GDI+的作用。

本章,我们会做出一个没有任何实质功能的异型界面,让大家入门。


本教程所需的知识,大概级别是你已经知道什么是函数,并且会使用最简单的 GUI 命令了。

由于这可能是大家第一次接触到GDI+,所以下面的代码几乎都是逐句解释的。看起来很长,其实全是注释讲解。

下面来张图,可以让大家了解下GDI+的一般流程。

现在看不懂没关系,入门以后,你会再回来看这张图的。


0.首先需要去下载一个Gdip库。注意,此库版本繁多,务必下载 Marius Șucan 的这个版本,因为这是最新最好支持最广泛的。你可以点击这里,或文末打包文件中找到。

00.特别特别注意,Marius Șucan 有两个项目,Quick-Picto-ViewerAHK-GDIp-Library-Compilation 。两个地方的Gdip库名字一样,版本号一样,大小也差不多,但其实前者Lib目录下的Gdip库才是最新的,也是本文所使用的!

1.加载库

; Uncomment if Gdip.ahk is not in your standard library
; 加载 GDI+ 库。
#Include, Gdip.ahk

2.初始化

; Start gdi+
; 初始化 GDI+ 。要用到 GDI+ 的各种功能,必须先初始化!
; 这里加个判断,检测一下初始化是否成功,失败就弹窗告知,并退出程序。
If !pToken := Gdip_Startup()
{
	MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system
	ExitApp
}
; 设置程序结束时,要跳转到名为 GdipExit 的标签去运行。通常在那里执行释放资源以及关闭 GDI+ 等收尾操作。
OnExit, GdipExit

3.创建界面

; Create a layered window (+E0x80000 : must be used for UpdateLayeredWindow to work!) that is always on top (+AlwaysOnTop), has no taskbar entry or caption
; 创建一个分层(又叫异型)的界面, +E0x80000 选项是必须的,不然等会图片贴不到这上面来。
Gui, 1: -Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs

; Show the window
; 显示界面。
; 注意,这里虽然叫显示界面,但因为使用了 +E0x80000 选项,所以此刻看起来还是什么都没有的,需要等会用 GDI+ 把图片贴上才能真正显示出东西来。
Gui, 1: Show, NA

; Get a handle to this window we have created in order to update it later
; 获取界面句柄。实际上也可以通过创建界面时使用 +Hwnd 选项获得句柄,两种方法都一样的。
hwnd1 := WinExist()

4.载入图片

; Get a bitmap from the image
; 载入等会用来做界面的图片。这里将会载入一个带透明背景的 png 图片,以便你观察异型的、中空的 GUI 是怎么做出来的。
; 在 GDI+ 中,对图片做任何处理,都要先把图片转换成 “pBitmap” 格式。
; 所以这里,我们就使用 Gdip_CreateBitmapFromFile() 函数,把图片直接转换成这种格式。
; 你可以理解为,马上我们要去美国了,你得先把人民币换成美元才能被那里的美国人接受。
pBitmap := Gdip_CreateBitmapFromFile(\"background.png\")

; Check to ensure we actually got a bitmap from the file, in case the file was corrupt or some other error occured
; 检验一下图片加载成功与否。
If !pBitmap
{
	MsgBox, 48, File loading error!, Could not load \'background.png\'
	ExitApp
}

5.获取图片信息

; Get the width and height of the bitmap we have just created from the file
; This will be the dimensions that the file is
; 获取图片的宽和高。看到了吧, GDI+ 中的函数,就是要提供 “pBitmap” 给它们,它们才工作。
Width := Gdip_GetImageWidth(pBitmap), Height := Gdip_GetImageHeight(pBitmap)

6.创建位图

; Create a gdi bitmap with width and height of what we are going to draw into it. This is the entire drawing area for everything
; We are creating this \"canvas\" at half the size of the actual image
; We are halving it because we want the image to show in a gui on the screen at half its dimensions
; 创建一个与设备无关的位图。什么叫与设备无关呢?
; 比如你创建一个和屏幕有关的位图,同时你的屏幕是256彩色显示的,这个位图就只能是256彩色。
; 又比如你创建一个和黑白打印机有关的位图,这个位图就只能是黑白灰色的。
; 设备相关位图 DDB(Device-Dependent-Bitmap)
; DDB 不具有自己的调色板信息,它的颜色模式必须与输出设备相一致。
; 如:在256色以下的位图中存储的像素值是系统调色板的索引,其颜色依赖于系统调色板。
; 由于 DDB 高度依赖输出设备,所以 DDB 只能存在于内存中,它要么在视频内存中,要么在系统内存中。
; 设备无关位图 DIB(Device-Independent-Bitmap)
; DIB 具有自己的调色板信息,它可以不依赖系统的调色板。
; 由于它不依赖于设备,所以通常用它来保存文件,如 .bmp 格式的文件就是 DIB 。
; 使用指定的宽高创建这个位图,之后不管你是画画也好,贴图也罢,就这么大地方给你用了。
hbm := CreateDIBSection(Width//2, Height//2)

7.创建DC

; Get a device context compatible with the screen
; 创建一个设备环境,也就是 DC 。那什么叫 DC 呢?
; 首先,当我们想要屏幕显示出一个红色圆形图案的话,正常逻辑是直接告诉显卡,给我在 XX 坐标,显示一个 XX 大小, XX 颜色的圆出来。
; 但 Windows 不允许程序员直接访问硬件。所以当我们想要对屏幕进行操作,就得通过 Windows 提供的渠道才行。这个渠道,就是 DC 。
; 屏幕上的每一个窗口都对应一个 DC ,可以把 DC 想象成一个视频缓冲区,对这个缓冲区进行操作,会表现在这个缓冲区对应的屏幕窗口上。
; 在窗口的 DC 之外,可以建立自己的 DC ,就是说它不对应窗口,这个方法就是 CreateCompatibleDC() 。
; 这个 DC 就是一个内存缓冲区,通过这个 DC 你可以把和它兼容的窗口 DC 保存到这个 DC 中,就是说你可以通过它在不同的 DC 之间拷贝数据。
; 例如,你先在这个 DC 中建立好数据,然后再拷贝到目标窗口的 DC 中,就完成了对目标窗口的刷新。
; 最后,之所以叫设备环境,不叫屏幕环境,是因为对其它设备,比如打印机的操作,也是通过它来完成的。
; 额外的扩展,CreateCompatibleDC() 函数,创建的DC,又叫内存DC,也叫兼容DC。
; 我们在绘制界面的时候,常常会听到说什么“双缓冲技术”避免闪烁,实际上就是先把内容在内存DC中画好,再一次性拷贝到目标DC里。
; 而普通的画法,就是直接在目标DC中边显示边画,所以就会闪烁。
hdc := CreateCompatibleDC()

8.把位图扔DC里

; Select the bitmap into the device context
; 学名上,这里叫做 “把 GDI 对象选入 DC 里” 。
; 为了方便理解呢,可以认为是 “把位图扔 DC 里”。
; 因为 DC 需要具体的东西才能显示嘛,所以得把东西扔里面去。
; 注意这个函数的特点,它把 hbm 更新了,同时它返回的值是旧的 hbm !
; 这里旧的 hbm 得存着,未来释放资源的时候需要用到。
obm := SelectObject(hdc, hbm)

9.创建画布

; Get a pointer to the graphics of the bitmap, for use with drawing functions
; G 表示的是一张画布,之后不管我们贴图也好,画画也好,都是画到这上面。
; 如果你是刚开始接触 GDI+ ,可能还没有完全弄懂这些东西的意思,所以这里总结一下基本流程。
; 初始化 GDI+ ----> 创建位图 ----> 创建 DC ----> 把位图扔 DC 里 ----> 创建画布
; 以上就是一个开始的定式,暂时不懂也没关系,记住就行了。
G := Gdip_GraphicsFromHDC(hdc)

10.设置画布模式

; We do not need SmoothingMode as we did in previous examples for drawing an image
; Instead we must set InterpolationMode. This specifies how a file will be resized (the quality of the resize)
; Interpolation mode has been set to HighQualityBicubic = 7
; 这里不需要设置之前例子中用到的抗锯齿选项,因为这是在操作图片。
; 这里需要设置图片缩放的插值模式,我们使用的是模式7,也就是二次立方高质量模式!
Gdip_SetInterpolationMode(G, 7)

11.把图贴到画布上

; DrawImage will draw the bitmap we took from the file into the graphics of the bitmap we created
; We are wanting to draw the entire image, but at half its size
; Coordinates are therefore taken from (0,0) of the source bitmap and also into the destination bitmap
; The source height and width are specified, and also the destination width and height (half the original)
; Gdip_DrawImage(pGraphics, pBitmap, dx, dy, dw, dh, sx, sy, sw, sh, Matrix)
; d is for destination and s is for source. We will not talk about the matrix yet (this is for changing colours when drawing)
; 把 pBitmap 画到 画布上。
; 整个函数的参数分别是 Gdip_DrawImage(画布, pBitmap, 新图x, 新图y, 新图宽, 新图高, 原图x, 原图y, 原图宽, 原图高, 矩阵)
; 最后的矩阵参数是给图像改变颜色之类用的,很高级,先不管它。
; 原图x, 原图y, 原图宽, 原图高
; 代表从原图的 (x,y) 这个坐标点开始,向右获得原图宽、向下获得原图高的图片数据
; 举例:当 原图x=50,原图y=100,原图宽=原图的宽,原图高=原图的高,那么实际取得的就是原图右下角部分。
; 举例:当 原图x=0,原图y=0,原图宽=原图的宽*0.9,原图高=原图的高*0.9,那么实际取得的就是原图左上角部分。
; 当原图选择了部分大小,而新图尺寸是原图大小,则会被放大填满。
; 新图x, 新图y, 新图宽, 新图高
; 代表新图在画布上的位置和大小。
Gdip_DrawImage(G, pBitmap, 0, 0, Width//2, Height//2, 0, 0, Width, Height)

12.显示界面

; Update the specified window we have created (hwnd1) with a handle to our bitmap (hdc), specifying the x,y,w,h we want it positioned on our screen
; So this will position our gui at (0,0) with the Width and Height specified earlier (half of the original image)
; 将 DC 上的内容显示在窗口上。此时,界面真正显示出来了。
; 注意,这里的宽高不能大于 CreateDIBSection() 时的宽高。
; 现在的层级关系是这样的,我们眼睛看到的是屏幕,屏幕是一个全透明的玻璃。
; 屏幕后面的是 DC 。而 DC 的后面,则是画布。
; 我们把 DC 想象成一张纯黑色的纸,中间掏空了部分。显然,透过黑纸掏空的部分,我们才能看到画布上的东西。
; 此处做视频的话,最好直接用两张纸做个演示模型,方便大家理解。
; 画布的坐标0点是相对 DC 的。而 DC 的坐标0点是相对屏幕的。
; 此时可以用 spy 工具观察,图片范围外是没有句柄的,也就是说,这是一个异型的界面。
UpdateLayeredWindow(hwnd1, hdc, 5, 0, Width//2, Height//2)

13.收工善后

; The bitmap we made from the image may be deleted
; 到此整个绘画工作就结束了,因此下面开始释放资源。
; 这里依然总结一下,结束的流程跟开始的流程基本一致,也是定式。
; 删除图片 ----> 删除画布 ----> 还原位图 ----> 删除 DC ----> 删除位图 ----> 关闭 GDI+
; 删除图片。
Gdip_DisposeImage(pBitmap)

; The graphics may now be deleted
; 删除画布。
Gdip_DeleteGraphics(G)

; Select the object back into the hdc
; 还原位图。
SelectObject(hdc, obm)

; Also the device context related to the bitmap may be deleted
; 删除 DC 。
DeleteDC(hdc)

; Now the bitmap may be deleted
; 删除位图。
DeleteObject(hbm)

Return

;#######################################################################
GuiClose:
GuiEscape:
GdipExit:
	; gdi+ may now be shutdown on exiting the program
	; 别忘了,我们最开始用 Gdip_Startup() 启动了,这里对应要用 Gdip_Shutdown() 关闭它。
	Gdip_Shutdown(pToken)
	ExitApp
Return

本章习题:画出异型界面后,自己用各种SPY工具,看看镂空的地方有没有句柄,看看镂空的地方算不算界面。


全部代码与库文件下载地址:

https://ahk.lanzoux.com/b01nypnuh

密码:1234

为TA充电
共{{data.count}}人
人已赞赏
AHKV1学习

《GDI+系列教程》第0章 —— 什么是GDI+ 它能干啥?

2021-1-22 19:05:29

AHKV1学习

1.3.2修饰符-神秘的符号(第一道壁垒)

2021-1-23 23:26:47

8 条回复 A文章作者 M管理员
  1. 1河许人

    我从头到尾敲了一遍,收获很多,老哥辛苦了。
    #Include Gdip_All.ahk

    if !pToken:=Gdip_startup()
    {
    MsgBox,48,GDI+错误!,GDI+无法启动。请确定你系统有GDI+
    ExitApp
    }
    Gui,1: -Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs

    Gui,1:Show,NA
    hWnd1:=WinExist()
    pBitmap:=Gdip_CreateBitmapFromFile(“background.png”)
    if !pBitmap
    {
    MsgBox,48,文件载入错误!,无法加载background.png
    ExitApp
    }
    Width:=Gdip_GetImageWidth(pBitmap)
    Height:=Gdip_GetImageHeight(pBitmap)
    hbm:=CreateDIBSection(Width//2,Height//2)
    hdc:=CreateCompatibleDC()
    obm:=SelectObject(hdc,hbm)
    G:=Gdip_GraphicsFromHDC(hdc)
    Gdip_SetInterpolationMode(G,7)
    Gdip_DrawImage(G,pBitmap,0,0,Width//2,Height//2,0,0,Width,Height)
    UpdateLayeredWindow(hWnd1,hdc,5,0,Width//2,Height//2)
    Gdip_DisposeImage(pBitmap)
    Gdip_DeleteGraphics(G)
    SelectObject(hdc,obm)
    DeleteDC(hdc)
    Deleteobject(hbm)
    Return

    OnExit,GdipExit

    GuiClose:
    Guiescape:
    GdipExit:
    Gdip_Shutdown(pToken)
    ExitApp
    Return

    • 1河许人

      等全部完事我再敲一遍,做总结

  2. 1河许人

    开始第二遍,同步把库汉化😁

    • 空

      gdip库已经封装好的有306个函数,没封装的147个,总共有453个函数,确定要汉化???而且,能搞gdip的人了,对于汉化的需求,我觉得应该远低于一些更基础的东西。

    • 1河许人

      汉化没多少东西的?就是名字还有参数

  3. safe

    DC全称是什么

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索