抗锯齿直线绘制方法(一)

抗锯齿直线绘制方法(一)

直线在数学上是一个抽象的概念,它是不占空间的,但在屏幕上绘制一条直线是以像素为空间单位的,所以绘制的直线呈现锯齿状,这样绘制的直线视觉效果不理想。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+绘制。
在这里插入图片描述

(未完待续)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值