抗锯齿直线绘制方法(一)
直线在数学上是一个抽象的概念,它是不占空间的,但在屏幕上绘制一条直线是以像素为空间单位的,所以绘制的直线呈现锯齿状,这样绘制的直线视觉效果不理想。Windows系统提供了Gdi+函数集,使用Gdi+函数绘制直线具有抗锯齿的效果,所绘制的直线看上去有平滑感,视觉效果比较理想。本文介绍如何不使用Gdi+函数,而在Gdi中直接绘制抗锯齿直线,使视觉效果与Gdi+函数绘制的直线相同,并确保绘图速度不差于Gdi+。
1. 抗锯齿直线绘制原理
如果我们用铅笔沿着尺子在纸上绘一条直线,但因为尺子不平整,所绘的直线有锯齿,这时我们就会用铅笔在有锯齿的地方进行均匀的涂抹,使直线看上去变得平滑,但直线的宽度会增加。在屏幕上绘制抗锯齿直线的原理与此相同,也是使用"涂抹"的方法消除锯齿,使直线具有平滑的视觉效果,但直线的宽度会增加1或2个像素。
一个像素宽度的直线经"涂抹"后最多变为三个像素宽度,为了描述"涂抹"后的三个像素值,我们定义一个ALPHA_DISTR3结构:
;----------------------------------
;颜色通量分配结构
;----------------------------------
ALPHA_DISTR3 STRUCT
alpha1 WORD ? ;第一点颜色通量(上值)
alpha2 WORD ? ;第二点颜色通量(中值)
alpha3 WORD ? ;第三点颜色通量(下值)
ALPHA_DISTR3 ENDS
也就是说,抗锯齿直线的每一个抽象点用三个实点来表示,分别称为alpha1、alpha2、alpha3,这三个值都是颜色通量值,也就是透明度,对应ARGB颜色单元中的"A"部分值。颜色通量分为255个级别,级别越大颜色越深,级别越小颜色越浅,零值为不可见。即颜色通量决定了"涂抹"的颜色深浅程度,三个颜色通量之和等于直线的颜色通量值,如一条直线的ARGB颜色为80ff0000h,表示该直线为红色,颜色通量为128(80h),则直线上任何一点的三个颜色通量之和均为128。在Gdi+中直线起止点的三个颜色通量之和小于直线的颜色通量,这样做法的目的是使直线看上去有一种圆滑的感觉,为了与Gdi+兼容,我们也使用这样做法。另外Gdi直线是不包括终点坐标的,如一条直线的起始坐标为(0,0)到(100,0),直线实际绘制到(99,0),共100个像素长度。但在Gdi+中是包括终点坐标的,也就是说实际绘制长度为101个像素长度,为了与Gdi+兼容,我们也使用这样做法。
例,一条45度对角线的颜色通量为255,则其起始点的颜色通量分配为ALPHA_DISTR3(0,143,48),终止点的颜色通量分配为ALPHA_DISTR3(16,175,0),中间各点的颜色通量分配均为ALPHA_DISTR3(16,191,48)。
那么如何确定一条直线中各点的颜色通量分配方案,最原始的办法是在纸上绘一条平滑的直线,再扫描到电脑中,读取该直线所有像素点的颜色通量(透明度)进行研究,找到其中的规则,建立抗锯齿直线模型。这种方法很费时,我们可以使用Gdi+函数绘制直线,读取该直线所有像素点的颜色通量(透明度)进行研究,这样就比较省时省力。
直线的数量是无限的,我们不可能对每一条直线都建立一个抗锯齿模型,但如果将直线分为几个有限的类别,对每一个类型的直线建立一个通用抗锯齿模型,使无限变为有限,则问题就可以解决。因为"抗锯齿"是一种视觉效果,在很多情况下使用模型法比计算法更现实。
2. 直线分类
为了建立直线抗锯齿模型,必须对直线进行分类。我们先将水平线、垂直线和45度对角线作为特殊类线,然后再对其它直线进行分类。由于很多直线是对称的,所以我们只关心第四象限45度内的直线(如图示1)。为了方便描述,我们指定第四象限的x和y轴长度都为100,并在y=50处将区域分为上下两个部分(图示1中的蓝色和红色)。
2.1 直线特征
(1)上部区域的每一条直线都是由若干水平子线段组成,水平子线段长度的计算公式为:
alen=dx/dy
dx=|x2-x1|
dy=|y2-y1|
如果dx/dy能整除,即无余数,则该直线的所有水平子线段长度均为alen。如果dx/dy不能整除,即有余数,则该直线由两种长度的水平子线段所组成,我们将首个水平子线段长度定义为alen1,另一个水平子线段长度定义为alen2,且alen2=alen1±1。alen2也称为间隔子线段。(见图示2)
(2)下部区域的每一条直线都是由若干对角子线段组成,对角子线段的长度(即水平宽度)的计算公式为:
alen=dx/(dx-dy)
dx=|x2-x1|
dy=|y2-y1|
如果无余数,则该直线的所有对角子线段长度均为alen。如果有余数,则该直线由两种长度的对角子线段所组成,我们将首个对角子线段长度定义为alen1,另一个对角子线段长度定义为alen2,且alen2=alen1±1。(见图示3)
2.2 根据直线特征进行分类
有了直线特征参数,就可以对直线进行分类。将由若干水平子线段组成的直线用"Lh-alen1"格式进行大分类标记,用"Lh-alen1-alen2"进行细分类标记。同理,将由若干对角子线段组成的直线用"Ld-alen1"格式进行大分类标记,用"Ld-alen1-alen2"进行细分类标记。
如:
dx/dy=100/19的alen1=5,alen2=6
dx/dy=100/21的alen1=5,alen2=4
上述两条直线的大分类标记为Lh-5,细分类标记分别为Lh-5-6、Lh-5-4。
经分析发现,alen1>=8时抗锯齿处理比较简单,所以我们将其指定为一个统一类,分别用"Lh-g8"和"Ld-g8"来表示,这样直线的分类数量变得很少,全部大分类如下:
Lh-g8、Lh-7、Lh-6、Lh-5、Lh-4、Lh-3、Lh-2、Lh-g8;Ld-7、Ld-6、Ld-5、Ld-4、Ld-3、Ld-2。
直线分类数量的减少有利于建立抗锯齿直线模型,全部模型总数据不足二千字节。
3. 基本结构和函数说明
本章节只介绍与本章节相关的几个结构和函数。相关的基本结构和函数如下:
;----------------------------------
;颜色通量3点分配结构
;----------------------------------
ALPHA_DISTR3 STRUCT
alpha1 WORD ? ;第一点颜色通量
alpha2 WORD ? ;第二点颜色通量
alpha3 WORD ? ;第三点颜色通量
ALPHA_DISTR3 ENDS
;-----------------------------
;直线平均分段计算结构
;-----------------------------
GDIX_LINEMEAN STRUCT
alen DWORD ? ;平均长度整数部
rem DWORD ? ;平均长度的余数
ndiv DWORD ? ;除数
sumr DWORD ? ;余数累加器
alen1 DWORD ? ;首个子线段长度
alen2 DWORD ? ;间隔子线段长度
GDIX_LINEMEAN ENDS
;---------------------------------------
;直线基本特征参数结构
;---------------------------------------
GDIX_LINEINFO STRUCT
zFlag DWORD ? ;志标位
pt1 POINT <> ;起始点坐标
pt2 POINT <> ;终止点坐标
x DWORD ? ;当前点X坐标
y DWORD ? ;当前点Y坐标
xy1 DWORD ? ;计算坐标变量的起始值
px QWORD ? ;步进变量地址
py QWORD ? ;计算变量地址
idx DWORD ? ;步进坐标的差值(x2-x1或y2-y1)
idy DWORD ? ;计算坐标的差值(y2-y1或x2-x1)
xinc DWORD ? ;idx的正负标志(1=正,-1=负)
yinc DWORD ? ;idy的正负标志(1=正,-1=负)
nlen DWORD ? ;步进方向长度(|x2-x1|或|y2-y1|)
slope DWORD ? ;斜率(百分比)
hSeg GDIX_LINEMEAN <> ;水平(垂直)分段参数
dSeg GDIX_LINEMEAN <> ;对角分段参数
GDIX_LINEINFO ENDS
; zFlag:
GDIX_HORLINE EQU 01h ;水平线
GDIX_VERLINE EQU 02h ;垂直线
GDIX_HVLINE EQU 03h ;水平或垂直线
GDIX_45LINE EQU 04h ;45度线
GDIX_HV45LINE EQU 07h ;水平线或垂直线或45度线
GDIX_QTLINE EQU 08h ;非45度的斜线
GDIX_LINESTEPX EQU 10h ;步进方向为X坐标
GDIX_LINESTEPY EQU 20h ;步进方向为Y坐标
;=========================================================
;计算直线的基本特征参数---用于抗锯齿绘线
;入: pLine=GDIX_LINEINFO结构。用于接收计算结果。
; x1,y1=直线始点坐标
; x2,y2=直线终点坐标
; zFlag=0: 保留
;出: RAX=直线类型:
; GDIX_HORLINE :水平线
; GDIX_VERLINE :垂直线
; GDIX_45LINE :45度线
; GDIX_QTLINE :非45度的斜线
; GDIX_LINESTEPX :步进方向为X坐标
; GDIX_LINESTEPY :步进方向为Y坐标
; =0: 长度为零(不需要绘制)
;---------------------------------------------------------
;说明:
; 以直线矩形区域的长距为步进方向,短距为计算方向,
; 并确保Y变量为递增。
;=========================================================
Gdix_GetLineInfo proc pLine:QWORD,x1:DWORD,y1:DWORD,\
x2:DWORD,y2:DWORD,zFlag:QWORD
LOCAL ss_Flag:DWORD
mov ss_Flag,GDIX_QTLINE ;返回值
mov [rcx.GDIX_LINEINFO].zFlag,0
;---保存原始坐标---
mov r10d,y2
mov [rcx.GDIX_LINEINFO].pt1.x,edx
mov [rcx.GDIX_LINEINFO].pt1.y,r8d
mov [rcx.GDIX_LINEINFO].pt2.x,r9d
mov [rcx.GDIX_LINEINFO].pt2.y,r10d
;---计算水平距和垂直距---
mov [rcx.GDIX_LINEINFO].xinc,1 ;X变量方向预置(1为递增,-1为递减)
mov [rcx.GDIX_LINEINFO].yinc,1 ;Y变量方向预置(1为递增,-1为递减)
mov eax,r9d
mov r11d,r10d
sub eax,edx ;x2-x1
mov [rcx.GDIX_LINEINFO].idx,eax
jge ss_1
neg eax ;|x2-x1|
mov [rcx.GDIX_LINEINFO].xinc,-1
ss_1:
sub r11d,r8d ;y2-y1
mov [rcx.GDIX_LINEINFO].idy,r11d
jge ss_2
neg r11d ;|y2-y1|
mov [rcx.GDIX_LINEINFO].yinc,-1
ss_2:
;---以长距作为步进变量---
cmp eax,r11d
jc ss_yy ;水平距<垂直距
jnz ss_3
mov ss_Flag,GDIX_45LINE ;45度线
ss_3:
test r11d,r11d ;r11d=y2-y1
jnz ss_4
mov ss_Flag,GDIX_HORLINE ;水平线
ss_4:
;---X坐标为步进变量---
or ss_Flag,GDIX_LINESTEPX ;步进方向为X坐标
mov [rcx.GDIX_LINEINFO].nlen,eax ;|x2-x1|
cmp [rcx.GDIX_LINEINFO].yinc,-1
jnz ss_6
;---对换起止坐标(确保Y坐标变量为递增)---
xchg edx,r9d ;x1与x2对换
xchg r8d,r10d ;y1与y2对换
neg [rcx.GDIX_LINEINFO].idx
neg [rcx.GDIX_LINEINFO].idy
neg [rcx.GDIX_LINEINFO].xinc
neg [rcx.GDIX_LINEINFO].yinc
ss_6:
;---设置计算变量值---
mov [rcx.GDIX_LINEINFO].x,edx
mov [rcx.GDIX_LINEINFO].y,r8d
mov [rcx.GDIX_LINEINFO].xy1,r8d
lea rax,[rcx.GDIX_LINEINFO].x
mov [rcx.GDIX_LINEINFO].px,rax ;步进变量地址
lea rax,[rcx.GDIX_LINEINFO].y
mov [rcx.GDIX_LINEINFO].py,rax ;计算变量地址
jmp ss_10
;---Y坐标为步进变量---
ss_yy:
or ss_Flag,GDIX_LINESTEPY ;步进方向为Y坐标
mov [rcx.GDIX_LINEINFO].nlen,r11d ;|y2-y1|
test eax,eax ;eax=x2-x1
jnz ss_7
mov ss_Flag,GDIX_VERLINE ;垂直线
ss_7:
cmp [rcx.GDIX_LINEINFO].yinc,-1
jnz ss_9
;---对换起止坐标(确保Y坐标变量为递增)---
xchg edx,r9d ;x1与x2对换
xchg r8d,r10d ;y1与y2对换
neg [rcx.GDIX_LINEINFO].idx
neg [rcx.GDIX_LINEINFO].idy
neg [rcx.GDIX_LINEINFO].xinc
neg [rcx.GDIX_LINEINFO].yinc
ss_9:
mov [rcx.GDIX_LINEINFO].x,edx
mov [rcx.GDIX_LINEINFO].y,r8d
;---设置计算变量值(注意: x与y对换)---
mov [rcx.GDIX_LINEINFO].xy1,edx
lea rax,[rcx.GDIX_LINEINFO].y
mov [rcx.GDIX_LINEINFO].px,rax ;步进变量地址
lea rax,[rcx.GDIX_LINEINFO].x
mov [rcx.GDIX_LINEINFO].py,rax ;计算变量地址
;---对换idx与idy的计算参数---
mov eax,[rcx.GDIX_LINEINFO].idx
xchg eax,[rcx.GDIX_LINEINFO].idy
mov [rcx.GDIX_LINEINFO].idx,eax
;---对换idx与idy的符号参数---
mov eax,[rcx.GDIX_LINEINFO].xinc
xchg eax,[rcx.GDIX_LINEINFO].yinc
mov [rcx.GDIX_LINEINFO].xinc,eax
ss_10:
;---线类型标志---
cmp [rcx.GDIX_LINEINFO].idy,0
jnz ss_11
mov [rcx.GDIX_LINEINFO].yinc,0 ;水平或垂直线
ss_11:
mov eax,ss_Flag
cmp [rcx.GDIX_LINEINFO].idx,0
jnz ss_12
xor eax,eax ;直线长度为零
ss_12:
or [rcx.GDIX_LINEINFO].zFlag,eax
;----------------------------
;计算斜率与分段参数(用于抗锯齿)
;----------------------------
test eax,eax
jz ss_0 ;线长度为零
test eax,GDIX_HORLINE or GDIX_VERLINE
jnz ss_hv
test eax,GDIX_45LINE
jnz ss_45
;---计算斜率---
mov eax,100
mov r8d,[rcx.GDIX_LINEINFO].idy
cmp r8d,0
jg ss_13
neg r8d
ss_13:
mul r8d
div [rcx.GDIX_LINEINFO].nlen
test eax,eax
jnz ss_14
mov eax,1 ;区别于水平线
ss_14:
mov [rcx.GDIX_LINEINFO].slope,eax ;斜率(百分比)
;---计算水平(垂直)分段参数---
xor rdx,rdx
mov eax,[rcx.GDIX_LINEINFO].nlen
div r8d ;dx/dy
mov [rcx.GDIX_LINEINFO].hSeg.alen,eax ;线段平均长度
mov [rcx.GDIX_LINEINFO].hSeg.rem,edx ;线段平均长度的余数
mov [rcx.GDIX_LINEINFO].hSeg.ndiv,r8d
mov [rcx.GDIX_LINEINFO].hSeg.sumr,0
mov r9d,eax
test edx,edx
jz ss_15 ;无余数时为均匀分配
inc r9d
shl edx,1
cmp edx,r8d
jle ss_15
mov r9d,eax
inc eax
ss_15:
mov [rcx.GDIX_LINEINFO].hSeg.alen1,eax ;首个分段长度
mov [rcx.GDIX_LINEINFO].hSeg.alen2,r9d ;间隔段分段长度
;---计算对角线平均宽(len=idx/(idx-idy))---
mov r9d,[rcx.GDIX_LINEINFO].nlen ;为正值
mov eax,r9d
sub r9d,r8d ;idx-idy
xor edx,edx
div r9d
mov [rcx.GDIX_LINEINFO].dSeg.alen,eax ;对角线平均宽
mov [rcx.GDIX_LINEINFO].dSeg.rem,edx ;对角线平均宽余数
mov [rcx.GDIX_LINEINFO].dSeg.ndiv,r9d ;|idx-idy|
mov [rcx.GDIX_LINEINFO].dSeg.sumr,0
mov r8d,eax
test edx,edx
jz ss_16
inc r8d
shl edx,1
cmp edx,r9d
jle ss_16
mov r8d,eax
inc eax
ss_16:
mov [rcx.GDIX_LINEINFO].dSeg.alen1,eax ;首个分段长度
mov [rcx.GDIX_LINEINFO].dSeg.alen2,r8d ;间隔段分段长度
mov eax,[rcx.GDIX_LINEINFO].zFlag
ret
;---水平或垂直线---
ss_hv:
mov [rcx.GDIX_LINEINFO].slope,0 ;斜率(百分比)
mov edx,[rcx.GDIX_LINEINFO].nlen
mov [rcx.GDIX_LINEINFO].hSeg.alen,edx ;线段平均长度
mov [rcx.GDIX_LINEINFO].hSeg.rem,0 ;线段平均长度的余数
mov [rcx.GDIX_LINEINFO].dSeg.alen,1 ;对角线平均宽
ret
;---45度线---
ss_45:
mov [rcx.GDIX_LINEINFO].slope,100 ;斜率(百分比)
mov [rcx.GDIX_LINEINFO].hSeg.alen,1 ;线段平均长度
mov [rcx.GDIX_LINEINFO].hSeg.rem,0 ;线段平均长度的余数
mov edx,[rcx.GDIX_LINEINFO].nlen
inc edx
mov [rcx.GDIX_LINEINFO].dSeg.alen,edx ;对角线宽度(包括终点)
mov [rcx.GDIX_LINEINFO].dSeg.rem,0 ;对角线平均宽或高的余数
ss_0:
ret
Gdix_GetLineInfo endp
;================================================
;混合色计算---快速计算
;入: Argb=前景色ARGB
; bRgb=背景色RGB
;出: EAX=混合后的RGB颜色
;------------------------------------------------
;注: 每种颜色分量的混合色计算方法,以红色为例:
; Red=bRed + (fRed-bRed)*A/255
; bRed=背景色,fRed=前景色,A=透明度。
;================================================
Color_AlphaBlend proc ;Argb:QWORD,bRgb:QWORD
xor r11,r11 ;结果
mov r8,rcx
shr r8,24 ;透明度A
mov r10,rdx ;背景色
;---Blue色混合---
mov eax,ecx
mov edx,r10d
and eax,0ffh
and edx,0ffh
sub eax,edx
imul r8d
shr eax,8 ;/256
add al,r10b
mov r11b,al
;---Green色混合---
shr r10d,8
shr ecx,8
mov eax,ecx
mov edx,r10d
and eax,0ffh
and edx,0ffh
sub eax,edx
imul r8d
shr eax,8 ;/256
add al,r10b
and eax,0ffh
shl eax,8
or r11d,eax
;---Red色混合---
shr r10d,8
shr ecx,8
mov eax,ecx
mov edx,r10d
and eax,0ffh
and edx,0ffh
sub eax,edx
imul r8d
shr eax,8 ;/256
add al,r10b
and eax,0ffh
shl eax,16
or eax,r11d
ret
Color_AlphaBlend endp
;===========================================================
;绘制一个抽象点的3个像素的混合颜色。
;入: hdc=DC句柄
; uColor=COLORREF颜色值
; pAlpha=ALPHA_DISTR3结构(颜色通量分配结构)
; rbx=GDIX_LINEINFO结构地址(直线参数结构)
; rsi=步进变量地址
; rdi=计算变量地址
;出: rsi所指变量值不变
; rdi所指变量值不变
;===========================================================
GdixLine_SetPixel3 proc hdc:QWORD,uColor:DWORD,pAlpha:QWORD
LOCAL ss_alpha1:DWORD
LOCAL ss_alpha2:DWORD
LOCAL ss_alpha3:DWORD
xor eax,eax
mov ax,[r8.ALPHA_DISTR3].alpha1
mov ss_alpha1,eax
mov ax,[r8.ALPHA_DISTR3].alpha2
mov ss_alpha2,eax
mov ax,[r8.ALPHA_DISTR3].alpha3
mov ss_alpha3,eax
;---第一点---
cmp ss_alpha1,0
jz ss_2
mov eax,[rbx.GDIX_LINEINFO].yinc
sub [rdi],eax
mov r8d,[rbx.GDIX_LINEINFO].y
mov edx,[rbx.GDIX_LINEINFO].x
call GetPixel ;hdc,x,y ;取背景色
mov ecx,ss_alpha1
shl ecx,24
or ecx,uColor
invoke Color_AlphaBlend,rcx,rax ;计算混合色
mov r9d,eax
mov r8d,[rbx.GDIX_LINEINFO].y
mov edx,[rbx.GDIX_LINEINFO].x
mov rcx,hdc
call SetPixelV ;hdc,x,y,color ;设置像素颜色
mov eax,[rbx.GDIX_LINEINFO].yinc
add [rdi],eax
;---第二点---
ss_2:
mov r8d,[rbx.GDIX_LINEINFO].y
mov edx,[rbx.GDIX_LINEINFO].x
mov rcx,hdc
call GetPixel ;hdc,x,y ;取背景色
mov ecx,ss_alpha2
shl ecx,24
or ecx,uColor
invoke Color_AlphaBlend,rcx,rax ;计算混合色
mov r9d,eax
mov r8d,[rbx.GDIX_LINEINFO].y
mov edx,[rbx.GDIX_LINEINFO].x
mov rcx,hdc
call SetPixelV ;hdc,x,y,color ;设置像素颜色
;---第三点---
cmp ss_alpha3,0
jz ss_out
mov eax,[rbx.GDIX_LINEINFO].yinc
add [rdi],eax
mov r8d,[rbx.GDIX_LINEINFO].y
mov edx,[rbx.GDIX_LINEINFO].x
mov rcx,hdc
call GetPixel ;hdc,x,y ;取背景色
mov ecx,ss_alpha3
shl ecx,24
or ecx,uColor
invoke Color_AlphaBlend,rcx,rax ;计算混合色
mov r9d,eax
mov r8d,[rbx.GDIX_LINEINFO].y
mov edx,[rbx.GDIX_LINEINFO].x
mov rcx,hdc
call SetPixelV ;hdc,x,y,color ;设置像素颜色
mov eax,[rbx.GDIX_LINEINFO].yinc
sub [rdi],eax
ss_out:
ret
GdixLine_SetPixel3 endp
;========================================================================
;装载直线模型数据---用于装载3点模型。
;入: pAlpha=ALPHA_DISTR3结构数组地址
; num=ALPHA_DISTR3结构个数,即要读入的点个数。
; alpha=源颜色通量(透明度)
; pData=直线模型定义数据表首址。必须为完整的3点模型。
;出: NO
;========================================================================
Gdix_LoadLineAlpha3pt proc pAlpha:QWORD,num:DWORD,alpha:DWORD,pData:QWORD
mov r11w,255 ;模型数据的比例值(1/255)
mov eax,edx
shl eax,1
mov r10d,edx
add r10d,eax ;num*3=模型数值个数
ss_lp1:
xor ax,ax
mov al,[r9]
mul r8w
div r11w
mov [rcx],ax
add rcx,2
inc r9
dec r10d
jnz ss_lp1
ret
Gdix_LoadLineAlpha3pt endp
基本结构和函数说明:
(1)ALPHA_DISTR3结构: 上面已经讲到过该结构,它是用于描述直线上一个抽象点对应的三个像素点的颜色通量分配,如一条45度线上每一个抽象点由三个像素点组成,每个像素点都分配一个颜色通量,alpha1为上面像素点的颜色通量,alpha2为中间点像素的颜色通量,alpha3为下面像素点的颜色通量。直线模型数据由ALPHA_DISTR3结构数组来保存。
(2)GDIX_LINEMEAN结构: 这是直线平均分段计算结构。在绘制直线时需要计算子线段和坐标,为了避免使用浮点计算,我们通过该结构来计算子线段和坐标。该结构中的alen1和alen2两个成员就是上文所讲的两个重要的直线特征值。
(3)GDIX_LINEINFO结构: 这是用于保存直线各类计算参数的结构,使用该结构可以统一直线的绘制方法。
(4)Gdix_GetLineInfo函数: 该函数计算直线的基本特征参数,并填写GDIX_LINEINFO结构。我们采用Gdi+的直线绘制方法,以直线矩形区域的长距为步进方向,短距为计算方向,并确保Y坐标变量为递增,使得直线模型数据能正确应用。
(5)Color_AlphaBlend函数: 这是颜色混合计算函数。既然抗锯齿是一种涂抹的技术,即将颜色按不同的深度与环境进行混合,在Gdi+中只要提供颜色的通量值便会自动完成,但在Gdi中需要自己来计算混合色。计算方法如下:
假设:
直线的RGB的红绿蓝颜色分别为fRed、fGreen、fBlue,颜色通量为A。
环境颜色(背景色)的红绿蓝颜色分别为bRed、bGreen、bBlue。
混合色的红绿蓝颜色分别为Red、Green、Blue。
则:
Red=bRed + (fRed-bRed)*A/255
Green=bGreen + (fGreen-bGreen)*A/255
Blue=bBlue + (fBlue-bBlue)*A/255
(6)GdixLine_SetPixel3函数: 这是绘制一个抽象点的函数。在绘制抗锯齿直线时,每一个抽象的点用三个像素来呈现,如果某一个像素的颜色通量为零则不绘制,所以该函数实际绘制1-3个像素点。该函数使用GetPixel、SetPixelV两个API来绘制像素,因为这两个API函数效率极低,直线绘制速度比Gdi+慢得很多,作为演示也无伤大雅 ,在最后我们将用新的方法替换该函数。
(7)Gdix_LoadLineAlpha3pt函数: 这是一个3点模型数据装载函数。
4. 特殊类直线的抗锯齿绘制
前面已讲过,水平线、垂直线和45度对角线作为特殊类线,这类线比较容易绘制。相关代码列表如下:
;----------------------------
;颜色通量分配结构
;----------------------------
ALPHA_LINE1 STRUCT
b ALPHA_DISTR3 <> ;起始点颜色通量分配
e ALPHA_DISTR3 <> ;终止点颜色通量分配
p1 ALPHA_DISTR3 <> ;中间点颜色通量分配
ALPHA_LINE1 ENDS
.data
;---------------------------------------------------
;3条特殊类直线模型数据---水平、垂直、45度线。
;---------------------------------------------------
Linex_model_hor db 0,191,0 ;水平线始点颜色通量分配
db 0,191,0 ;水平线终点颜色通量分配
db 0,255,0 ;水平线中间颜色通量分配
Linex_model_ver db 0,159,0 ;垂直线始点颜色通量分配
db 0,223,0 ;垂直线终点颜色通量分配
db 0,255,0 ;垂直线中间颜色通量分配
Linex_model_45 db 0,143,48 ;45度线始点颜色通量分配
db 16,175,0 ;45度线终点颜色通量分配
db 16,191,48 ;45度线中间颜色通量分配
.code
;============================================================
;绘制抗锯齿直线---水平或垂直线
;入: hdc=DC句柄
; argb=ARGB颜色
; zFlag=保留
; rbx=GDIX_LINEINFO结构。包含直线特征参数。
; rsi=指向步进变量
; rdi=指向计算变量
;出: EAX=1: 成功
;-----------------------------------------
;注: rbx,rsi,rdi,r12寄存器由调用者保护。
; 用SetPixelV函数绘制时argb参数实上是由COLORREF的高位加A组成,
; 即ABGR。当用新的方法替换SetPixelV,此时argb就量ARGB颜色。
;============================================================
Gdix_DrawLine_hor proc hdc:QWORD,argb:QWORD,zFlag:DWORD
LOCAL ss_alp:ALPHA_LINE1
LOCAL ss_alpha:DWORD
LOCAL ss_color:DWORD
LOCAL ss_n:DWORD
mov eax,[rbx.GDIX_LINEINFO].nlen
mov ss_n,eax ;步进长度
;---颜色处理---
mov eax,edx
shr edx,24 ;颜色通量
mov ss_alpha,edx
and eax,0ffffffh
mov ss_color,eax
;---装入模型数据---
lea r9,Linex_model_hor
test [rbx.GDIX_LINEINFO].zFlag,GDIX_HORLINE
jnz ss_2 ;是水平线
lea r9,Linex_model_ver ;是垂直线
ss_2:
invoke Gdix_LoadLineAlpha3pt,ADDR ss_alp,3,ss_alpha,r9
;---绘起始点---
invoke GdixLine_SetPixel3,hdc,ss_color,ADDR ss_alp.b
jmp ss_nt
ss_lp1:
invoke GdixLine_SetPixel3,hdc,ss_color,ADDR ss_alp.p1
ss_nt:
;---计算下一点坐标---
inc DWORD PTR [rsi]
dec ss_n
jnz ss_lp1
;---绘终止点---
invoke GdixLine_SetPixel3,hdc,ss_color,ADDR ss_alp.e
mov eax,1
ss_0:
ret
Gdix_DrawLine_hor endp
;=========================================================
;绘制抗锯齿直线---对角度(45度线)
;入: hdc=DC句柄
; argb=ARGB颜色
; zFlag=0: 保留
; rbx=GDIX_LINEINFO结构。包含直线特征参数。
; rsi=指向步进变量
; rdi=指向计算变量
;出: EAX=1: 成功
;-----------------------------------------
;注: rbx,rsi,rdi,r12寄存器由调用者保护。
;=========================================================
Gdix_DrawLine_Ang45 proc hdc:QWORD,argb:QWORD,zFlag:DWORD
LOCAL ss_alp:ALPHA_LINE1
LOCAL ss_alpha:DWORD
LOCAL ss_color:DWORD
LOCAL ss_n:DWORD
mov eax,[rbx.GDIX_LINEINFO].nlen ;步进长度
mov ss_n,eax
;---颜色处理---
mov eax,edx
shr edx,24 ;颜色通量
mov ss_alpha,edx
and eax,0ffffffh
mov ss_color,eax
;---装入模型数据---
invoke Gdix_LoadLineAlpha3pt,ADDR ss_alp,3,ss_alpha,ADDR Linex_model_45
;---绘起始点---
invoke GdixLine_SetPixel3,hdc,ss_color,ADDR ss_alp.b
jmp ss_nt
ss_lp1:
invoke GdixLine_SetPixel3,hdc,ss_color,ADDR ss_alp.p1
ss_nt:
;---计算下一点坐标---
mov eax,[rbx.GDIX_LINEINFO].xinc
add [rsi],eax
inc DWORD PTR [rdi]
dec ss_n
jnz ss_lp1
;---绘终止点---
invoke GdixLine_SetPixel3,hdc,ss_color,ADDR ss_alp.e
mov eax,1
ss_0:
ret
Gdix_DrawLine_Ang45 endp
;=======================================================
;绘制抗锯齿直线---类似于Gdi+中的GdipDrawLineI函数
;入: hdc=DC句柄
; x1,y1=起始点坐标
; x2,y2=终止点坐标
; Argb=ARGB颜色
;=======================================================
Gdix_DrawLine proc hdc:QWORD,x1:DWORD,y1:DWORD,\
x2:DWORD,y2:DWORD,Argb:DWORD
LOCAL ss_rsi:QWORD
LOCAL ss_rdi:QWORD
LOCAL ss_rbx:QWORD
LOCAL ss_r12:QWORD
LOCAL ss_Info:GDIX_LINEINFO
mov ss_rsi,rsi
mov ss_rdi,rdi
mov ss_rbx,rbx
mov ss_r12,r12
lea rbx,ss_Info
;---RGB颜色转换为COLORREF颜色值---
mov eax,Argb
mov r10d,eax
and eax,0ffffffh
ror ax,8
rol eax,16
ror ax,8
ror eax,8
and r10d,0ff000000h
or eax,r10d
mov Argb,eax
;---计算直线特征参数---
invoke Gdix_GetLineInfo,rbx,x1,y1,x2,y2,0
test eax,eax
jz ss_out ;长度为零
mov rsi,ss_Info.px ;指向步进变量
mov rdi,ss_Info.py ;指向计算变量
;---分类分支绘制---
test eax,GDIX_HV45LINE
jnz ss_hv45 ;水平线、垂直线或45度线
;-----------------
;待完善
;-----------------
jmp ss_out
;---水平线或垂直线或45度线---
ss_hv45:
test eax,GDIX_45LINE
jnz ss_45
;---水平线或垂直线---
invoke Gdix_DrawLine_hor,hdc,Argb,0
jmp ss_out
;---45度线---
ss_45:
invoke Gdix_DrawLine_Ang45,hdc,Argb,0
ss_out:
mov r12,ss_r12
mov rbx,ss_rbx
mov rsi,ss_rsi
mov rdi,ss_rdi
ret
Gdix_DrawLine endp
代码说明如下:
(1)ALPHA_LINE1结构: 用于装载特殊类直线模型。该类直线模型只有3个点,即起始点、终止点和中间点。
(2)Linex_model_hor、Linex_model_ver、Linex_model_45分别为水平线、垂直线和45度对角线的模型数据。该模型为3点模型格式,即每一点为3个值,分别对应一个ALPHA_DISTR3结构,但为了节省内存我们使用字节数据,装载时再转换为WORD数据。模型数据中的每一个值为颜色通量分量,三个分量之和为255,但起止点例外。这些数据在装载到ALPHA_DISTR3结构数组时,颜色通量值转换为实际的直线颜色通量值,即如果直线的颜色通量为128,则三个分量之和也为128。所以模型数据中的每一个值其实是以255为基数的比例值。
(3)Gdix_DrawLine_hor、Gdix_DrawLine_Ang45函数分别绘制水平线、垂直线和45度对角线。
(4)Gdix_DrawLine函数: 这是一个绘制抗锯齿直线的总调用入口函数。当前该函数不是完整的代码,在后续章节中逐步完善。
5. 特殊类直线抗锯齿绘制测试
测试函数共为两个,Gdip_DrawLine函数使用Gdi+绘制直线,用于与Gdi进行比较。
;================================================
;用Gdi+函数绘制直线---用于比较
;入: hdc=DC句柄
; x1,y1=起始点坐标
; x2,y2=终止点坐标
; Argb=ARGB颜色
;================================================
Gdip_DrawLine proc hdc:QWORD,x1:DWORD,y1:DWORD,\
x2:DWORD,y2:DWORD,Argb:DWORD
LOCAL ss_Graph:QWORD
LOCAL ss_Pen:QWORD
;---创建Gdi+绘图对象---
invoke GdipCreateFromHDC,hdc,ADDR ss_Graph ;创建绘图对象
;---设置平滑参数---
mov edx,SmoothingModeAntiAlias8x8 ;SmoothingModeAntiAlias
invoke GdipSetSmoothingMode,ss_Graph,edx ;平滑度
;---创建Gdi+画笔对象---
invoke GdipCreatePen1,Argb,0,0,ADDR ss_Pen ;创建颜色画笔
;---绘线---
invoke GdipDrawLineI,ss_Graph,ss_Pen,x1,y1,x2,y2
;---清理---
invoke GdipDeletePen,ss_Pen ;释放画笔
invoke GdipDeleteGraphics,ss_Graph ;释放绘图对象
ret
Gdip_DrawLine endp
;================================================
;Gdi抗锯齿直线绘制测试
;入: hdc=设备上下文句柄
;================================================
Gdpx_LineTest1 proc hdc:QWORD
;---绘制水平线---
invoke Gdix_DrawLine,hdc,10,10,140,10,0ffff0000h ;Gdi方法
invoke Gdip_DrawLine,hdc,10,20,140,20,0ff0000ffh ;Gdi+方法
;---绘制垂直线---
invoke Gdix_DrawLine,hdc,10,30,10,150,0ffff0000h ;Gdi方法
invoke Gdip_DrawLine,hdc,20,30,20,150,0ff0000ffh ;Gdi+方法
;---绘制45度线---
invoke Gdix_DrawLine,hdc,40,40,140,140,0ffff0000h ;Gdi方法
invoke Gdip_DrawLine,hdc,40,50,140,150,0ff0000ffh ;Gdi+方法
ret
Gdpx_LineTest1 endp
测试效果如图示4,其中红色线由Gdi绘制,蓝色线由Gdi+绘制。
(未完待续)