劃線程式
劃線程式
曾慶潭 Ching-Tang Tseng
ilikeforth@gmail.com
Hamilton, New Zealand
16 February 2026
要電腦執行在兩點間劃出一條直線的程式設計,看似簡單,實不單純,想用電腦控制外部機械,劃出一條直線,狀況相同。有許多劃線的方法曾被大家探討,但在理論與實務間存在著一些問題,要實作後才能體會問題所在,理論根據可以任找一個,程式工具也可以任選一種,實現它的過程所會遭遇的問題,則可能都是一樣的。
本文探討一份已在美國申請取得專利之公開資料的內容,並以 eForth32 於 DOS 環境內的 EGA 可繪圖模式為基礎,進行程式測試設計,文內記述了測試結果、遭遇的問題、及其解決方法。
專利資料是公佈於網上的公開資料,欲正式使用者,必須自行接洽專利擁有者。本文僅對這份專利的內容進行探討,但絕不實際運用,因為,文末另附乙份沒有專利問題的標準 FORTH 程式,可以毫無問題的直接應用。
這個主題在許多方面有利用價值,例如:二維系統中的機器運動控制──焊接、繪圖、打孔、定位、置放零件…等眾多電腦自動控制機械中,均可派上用場,若延伸發展至三度空間,則可供機器人運動控制之用。
1 原理與依據
與電腦系統相關的劃線環境,可以說幾乎全是由方格子柵樣的點所構成的,假設我們只能用這些微觀不連續的點來形成兩個座標間的一條線,應如何劃?
我們取 BASIC 語言,其程式具有幾乎等同於不改數學式子的特點,來解釋這個問題,就非常容易了解,如果將其簡化成最容易解釋的幾何圖與數學式子,就如同下述:
假設如圖一所示,在 ( X1 , Y1 ) 與 ( X2 , Y2 ) 兩點間欲劃之線應由許多相鄰卻不連續的點所構成,其中各個點的座標為 ( X , Y )。
根據幾何相似三角形原理,很容易看出打算劃出之點,其在線上的座標被表示為 ( X , Y ) ,而 Y 之值為:
Y = Y1 + ( ( Y2 – Y1 ) * ( X – X1 ) / ( X2 – X1 ) )
以 BASIC 來寫程式時,其結果為:
FOR X = X1 TO X2
Y = Y1 + ( ( Y2 – Y1 ) * ( X – X1 ) / ( X2 – X1 ) )
PLOT ( X , Y )
NEXT X
圖一
以上的敘述,可以算是已經解釋完了兩點間劃一條直線的基本原理,但實際情況絕非如此單純。首先,實際的整數數位環境中,通常未必每次都剛好有一可劃之點的座標,剛好等於算出來的 ( X , Y ) 非整數數位座標,其次,當由一點逼近到另一點的方式必須由幾條線段完成時,必須講求均勻的效果,程式設計者得為電腦設定一套計算完畢後劃那一點的規則,最後才會有較佳結果,這些都是劃線方法強調的重點。至於利用浮點數計算能力,先行算出該繪之點的座標,再利用浮點數轉換成整數的技巧達到目的的方法,則不在本文討論之列,因為,經常會遭遇到的環境,通常都缺乏浮點數計算能力。
我們絕對不應當將兩點間劃一條直線視為理所當然的事情,以為直接叫用系統中已經設計好的劃線指令來用就可以了,螢幕並不代表所有的環境,要到螢幕以外的地方去劃一條直線,程式必須自己設計,通常不是現成可取的。反過來講,如果掌握了劃直線程式的機制,那麼想要在任何可顯示圖形的螢幕上劃直線,就不困難,如此便形成探討這個主題的價值,也莫怪有人以此申請專利,良法不願隨便公開了。本文不想侵犯專利,文內說明了引用來源,但前述已經強調,理論與實務間存在差距,實作後才能有所體會與發現,細讀此文,輔以自行磨練,以後面對相同問題時,就能顯現本文之效用。
作者係先選定主題後才去尋找依據,並透過網路尋找答案,可依據的理論不勝枚舉,本文選取一個較佳的方法,摘要其中最主要的文字敘述,作為原理依據。謹詳實記錄其來源如下:
網頁: www.patenstorm.us/patents/6847362 (附記 : 我於 20260216 進行核查操作後確定這個網頁已經失聯)
Inventors: Lu, Chung-Yen Yao, Jo-Tan ( Taipei, Taiwan )
根據:
1. A fast line drawing method, comprising the steps of:
(a) receiving the coordinates of two end points as (x1,y1) and (x2,y2);
(b) assigning the coordinate (X,Y) of a current point as (x1,y1), computing
the differences of x and y coordinates of the end points as D(x) and
D(y), assigning Q as the integer part of D(x) over D(y), and
assigning the initial sum of error E as -D(x), and two constants K1
and K2 as 2*D(y) and 2*Q*D(y);
(c) checking whether the x-coordinate of the current point (X) has reached
the x-coordinate of the end point (x2);
(d) testing whether the value of E is non-negative;
(e) drawing a point at the current point if E is negative, updating the
x-coordinate of the current point (X) and E by X+1 and E+K1 respectively,
and returning to step (c);
(f) updating the y-coordinate of the current point (Y) and E by (Y+1) and
(E-2*D(x)) respectively if E is non-negative;
(g) checking whether the x-coordinate of the last of Q points (X+Q-1) is
less than the x-coordinate of the end point (x2);
(h) drawing a span of pixels from (X,Y) to (X+Q-1,Y) if the x-coordinate of
the last of Q points (X+Q-1) is less than the x-coordinate of the end
point (x2);
(i) drawing a span of pixels from (X,Y) to (x2,Y) if the x-coordinate of
the last of Q points (X+Q-1) is not less than the x-coordinate of the end
point (x2); and
(j) updating the x-coordinate of the current point (X) and E by X+Q and
E+K2 respectively, and returning to step (c).
2. The method as claimed in claim 1 further terminating the line drawing if
the x-coordinate of the current point reaches the x-coordinate of the end
point (x2).
上列資料來源中,附帶了相關的流程圖,再引用如圖二所示。原資料中也附有以 C 語言寫成的相關程式,如表一所示。展示程式當然不能執行,只能供作參考,原因為何?與前列的 BASIC 語言說明程式一樣,只是輔助的說明工具。實際設計出程式,才能了解理論的說明資料與實際應用上的差異,這也是本文實現程式設計最主要的重點所在。
2 系統選擇
接著,我們可以回到 FORTH 的領域了。為了實現這個程式,作者斟酌了許多可選用的軟、硬體,最後選定以 eForth32 在 DOS 環境的 EGA 模式下模擬,實際顯示功能與效果。為說明之須,並易於了解,盡量使用最簡單的指令,不修飾程式的美觀及執行速度,但求促其實現,以供日後參考。
eForth 系統尚未完全可以使用於當前的個人電腦硬體與普遍被採用的軟體作業系統中, FORTH 界有 Win32Forth 更適合這種環境,我亦相信 Win32Forth ,可以更容易達到目的。但根據我們多年的經驗,如果一向只用系統現成提供的功能,使用者便失去了經由 FORTH 了解電腦相關技術的機會,尤其是當系統功能多如牛毛時,使用者容易失去方向。 FORTH 老手一向領著系統走,而非被系統領著走,否則如何奢談操控電腦?更何況本文的主題幾乎是只用最簡單的 FORTH 基本指令就可以完成,殺雞焉用牛刀?若為日後的實務著想, eForth 相當適當。
eForth 的出現是 FORTH 發展的一大創見,其可貴的程度有甚於為 FORTH 指令爭取出 ANSI 標準,因為前者未必為必然的,而後者可以說是必然的,它的出現,就好像是幾何原理的發展到達最後成熟的程度時,數學家歸納出五個基本公設與一些基本定義,然後就可以推衍出所有的定理與定律, eForth 就像是這五個公設,使 FORTH 更成熟了, ANSI 標準卻像那些基本定義,也有其必要性,使 FORTH 更完整了。
圖二
很可惜,有一段相當長的時日,因微軟作業系統的出現,令 FORTH 的發展受到阻礙,而這又恰巧是 eForth 初期誕生的階段,已經十幾年了, FORTH 界人人都知道它可以有很好的延伸性發展,卻僅有少數人做了後續工作。它出現的當年,作者曾用它來為一個 8086 族的 V25 CPU 單板電腦實現 FORTH 功能,知其基本性能。
表一
而 eForth32 的出現又稍解了這種遺憾,它刻意不改最原始的 eForth 基本精神,包括指令名稱與系統變數,卻繼續發展出許多難能可貴的必要系統功能,還是一個 16 位元環境下的 32 位元系統,精簡扼要,事隔十多年仍能令人愛不釋手,自然有其特色。 FORTH 的愛用者通常都是站在巨人的肩膀上往前看,許多巨人都令人尊敬,尤其該尊重他們的不吝公開,引用創作時,就應避免竄改原始設計,盡量站在巨人的肩膀上繼續發揮。
在 eForth32 中我們以其已經具有在 EGA 模式下繪出一點的功能做基礎,這個基本指令在其他相類似應用中,相當於一隻筆或一個刀具的放下動作,可以想像成在機臺上弄出一個點來,指令名為 EGA! ,它是一個作者自行設計完全獨立的低階指令,執行前要在堆疊上放妥三個參數:顏色碼及 X 與 Y 的座標值,至於要在螢幕上切換繪圖模式或文字顯示模式,作者仍叫用了 BIOS 中的 INT10 功能, INT10 功能的參考文件已經公佈不少了,足供我們所須,在 eForth32 中我們只要用到作者設計之 EGA 及 TEXT 兩個簡單相關宣告指令便足夠了,使用這兩個指令時,不須要先安放執行參數,作者未提供劃線指令,我們恰能為其增添一點貢獻,上列三個指令設計在 EGA.4 檔案中,功能摘要如下:
EGA ( -- ) 切換至 EGA 繪圖模式
TEXT ( -- ) 切換至文字顯示模式
EGA! ( colour, X , Y -- ) 繪出一個點
必須額外強調的是,凡涉及 IBM 硬體與 Microsoft 軟體的部份都是有時間性的,不能永遠流傳使用,日後軟硬體隨時代演進之後,這些部份僅供參考。
3 程式設計過程摘要
根據圖二中的流程圖,直接翻譯成 FORTH 程式並不困難,然而實際程式中必須添加許多部份,才能完成設計,為節省篇幅,下列表二直接列出一個可以執行的最後結果,然後我們再來解說發展過程。值得強調的是,發展過程就是採用了 FORTH 精神,可以快速的即時測試、除錯、改寫、添加以至於痛快地完成。
首先,我們必須接受一個事實,那就是我們根本不可能一次就完成由文字敘述到最終程式的翻譯編寫工作,出錯絕對難免,甚至於因為軟體動到了硬體,不斷地造成當機現象也絕對難免,要歸原(Reset) 硬體系統,重新開機。
初期譯寫出來的程式,要想很快的就能由實際硬體看到結果,大慨非屬 FORTH 程式語言不可了,這是有立即執行功能之程式語言的好處,莫管它程式成敗與否?人人都喜歡能夠趕快試一試程式的特殊性能,因為除錯要根據眼觀現象後才能再作決定,看完顯示後才能想問題,快不起來就影響發展,別種程式語言的產出,因此不如 FORTH ,實作可以體會。
在程式設計的小技巧上,只要問題不太複雜,指令名稱並不需要非得使用很貼切的全名,若以兩個字母當作指令名稱就不容易衝突,那麼,程式內就只用兩個字母作為名稱,要不然三個也可以,以前 PolyForth 就習慣只留三個字母,大家依然喜歡使用這個系統。
單純根據原始資料直譯出來的 FORTH 程式,基本上會有問題,尤其是在邊界情況,例如:末點與原點的X座標相同時,就算Y座標不同也不繪了。程式設計時,要在主要流程的關鍵點,進行例外處理,根據經驗,程式設計時的邊界效應,通常會是評鑑測試時的重點,果不其然,這裡就是如此。
另外一個更為嚴重的問題是:原始資料講的是一個通用情況,在幾何數學中,指的是只在X─Y平面座標的第一個象限有效。因此,當程式辛苦的除錯、測試完畢後,可以發現,只有當末點的 X 與 Y 座標值均大於原點時,繪線才會有效,這個現象依靠 FORTH 的立即執行功能,可以立即發現。謹慎思考之後,決定保留原專利的基本精神,另行設法補充解決。簡而言之,是採用了所謂的『在虛像中計算,在實境中劃點』之小技巧,完成了設計。
以幾何圖形思考這個問題時,原點無論想向四個象限中的那一個方向劃一直線,都可以在第一象限中找到唯一一條對應線的虛影像,只不過兩者之間的座標值有所不同,也不單純只是正負號的不同而已,但是可以經由一套單純的算術換算,建立對應關係。程式設計的發展過程中,這個問題以增設記憶變數,留存旗標值,來決定應當繪點的象限,然後根據固定的核算規則,算出實際應繪點的座標值,才實際繪點,演算機制永遠保留在第一象限中,新加的程式只須於編輯時插入適當位置便可。
此外,另有一些實作、實用後才能發現的問題值得記述:
早年 FORTH 使用者慣用的有限迴路指令是 DO ----- LOOP ,長久的使用經驗告訴我們,在實際環境中,讓有限迴路執行由負數過零點的情況,是會出問題的,才有後來有限迴路指令改用 FOR ----- NEXT 的演進,本程式設計時也免不了有此問題,因而體會出演進的意義。我們想控制的物品通常是處於正整數的實體,不是負數環境中的虛體,排除它會好用些。舊的 DO ----- LOOP 有限迴路指令在執行前,需指定兩個數字,作為迴路的上下限,可能會有過零點的情況,新的 FOR ----- NEXT 有限迴路指令在執行前,只須提供單一個執行限量,可以避免過零點的情況發生,實際上通常就是一個要執行幾次迴路的數字,符合實際。
有限迴路內可用的指標 I 或 R@ 也未必一定得在迴路中使用,基本起始值可以經由堆疊傳遞入迴路,然後在其中再設定加減量,來達到每次加減固定量的要求,自動控制經常要用到這項技巧,不可不知。本程式在發展過程中還經驗了一個很好的實務,經由 FORTH 的即時性能,曾經發現了程式執行時,繪線的方式是由末點繪向原點,這與該要求的實際情況不符,形同被控制機具直線運動的逼近方向相反,它可以由迴路中的變量是增或減來解決,但是,如果系統沒有方便到擁有 FORTH 的即時性能,發展過程必定很不方便。
新近電腦速度很快,記憶容量很多,對應用發展有利。可是,我們有時會碰到一些情況不領這些情,例如:自動控制機械的動件通常是有重量的,物理上說是具有慣量,那麼每逢要控制機具運動時,就不得不考慮速度問題,而且通常是並不想讓機具運動的速度,快到像電腦決定事情的速度那般快,有慣量的物體,是不能夠像慣量幾乎可以忽略掉的電子掃描光點,那樣子被指揮運動的。程式設計時,還得希望程式被執行時,能夠配合要求慢下來,以便觀察真正的效果, FORTH 面對此情況時的便利性更不待言,更容易進行調適設計。以前我們常設計一個被稱之為 Delay 的指令,它強迫 CPU 執行一定量的迴路,而迴路沒有內容,俗話說就只是乾耗,使用者自己去體會,感覺出大約一秒需多少量來做為一個延遲單位,此指令便能被插入程式中,來達到執行時變慢的效果,以便執行效果能夠被觀察。
前面已經強調,使用的是一個模擬環境,自然就得考慮繪線環境有其限度, eForth32 有 32 位元的處理功能,單整數可以大到四十二億以上 (4,294,967,295) 而不再是 65535 ,這也代表此量的尺度足夠涵蓋人類可能面對的實體點的數量,套句俗話說,它不僅只是幾萬點的解析度,而是四十幾億點,夠大到用不完了。然而本程式在實際繪圖時,EGA硬體的設計只能讓使用者在 640X350 點形成的平面空間自由操作,仔細研究繪出一點的指令 EGA! 便能明白這項限制,平面空間是二維的體系,每一點對應到實際的硬體記憶體時,要考慮記憶體的排列秩序是一維的結構,因此指令中便得大量設計這種對應關係的換算處理,本文僅借用原作者的設計,不打算深入解釋。
前述還曾提到,如果所繪之線必須由幾條線段連接完成時,這些線段要能均勻,所用方法才能稱好,要快速觀察後得知效果, FORTH 又幫了大忙。
eForth32 本身因強調精簡,有些性能會讓使用者感到不太理想,例如:它的執譯程式(Interpreter)就簡化到幾乎不可再簡的程度,萬一輸入指令須要的堆疊參數不足時,螢幕上出現許多的亂碼,原因是此系統刻意省了執行幾個檢查系統狀況的指令,但無傷於系統的繼續使用。 1987 年四月份『FORTH期刊』拙作 “Forth結構與執行原理” 一文中提到,一個去蕪存菁的執譯程式,結構可以相當簡單,而 1990 年公開推出的 eForth ,就進行這樣的安排,若非它的實作與實現,我們不易體會與瞭解這樣安排的結果會是如何?我們必須瞭解執譯程式是一個永無休止的執行迴路,令 FORTH 在每執行一個指令後,都得循迴一次,如果迴路內工作做多了,當然耗費時間,系統速度就會變慢。 FORTH 是透通且很有彈性的,修改它並不困難, eForth32 提供了很好的工具,容易完成。 M.4 是一個完整的蛻變程式,以前要等到學會 FORTH 的後期,才有機會接觸第四級難度之所謂的蛻變編譯(Meta-compiler),以蛻變程式來產生標的系統(Target system), eForth32 給予作者最大的印象是 : 一試用這個系統,就可以先執行蛻變編譯出系統自身,然後試用無誤,令人痛快高興?
eForth32 當然仍有些欠缺,難與發展較為完善的 Win32Forth 相比,但因此卻給予喜歡玩系統功能的愛用者很好的機會,它留下了許多可以繼續發展的空間,例如浮點算術的功能,與視窗系統間的連接功能,甚至於本身執行速度的改善,與人機界面的友善要求,都還很值得繼續發展。
\ FAST LINE DRAW
CODE CRT ( n --- \ send a crt mode command )
BX AX MOV
$10 INT
BX POP
DX POP
NEXT
END-CODE
: EGA ( --- \ setup 640 by 350, 16 color graphics mode )
16 CRT 0 20 AT ;
: TEXT ( --- \ setup 80 column 24 line text mode )
3 CRT ;
CREATE MASKS
$80 C, $40 C, $20 C, $10 C,
8 C, 4 C, 2 C, 1 C,
CODE EGA! ( I X Y --- \ DIRECT TO EGA no BIOS )
( bx=y*80)
BX AX MOV BX 1 SHL BX 1 SHL AX BX ADD ( bx=y*5 )
BX 1 SHL BX 1 SHL BX 1 SHL BX 1 SHL ( bx=y*5*16)
( ax=x/8)
CX POP 2 # SP ADD CX AX MOV AX 1 SHR AX 1 SHR AX 1 SHR
( bx=y*80 + X/8)
AX BX ADD
( es=video memory base address)
$A000 # AX MOV AX ES MOV
( set video mode 2)
$3CE # DX MOV 5 # AL MOV DX AL OUT
DX INC ( 3cf) 2 # AL MOV DX AL OUT ( mode 2)
DX DEC ( 3ce) 8 # AL MOV DX AL OUT ( select mask register)
( get the proper bit mask)
7 # CX AND MASKS # DI MOV CX DI ADD
CS: 0 [DI] AL MOV
DX INC ( 3cf) DX AL OUT ( column bit mask)
( ????)
ES: 0 [BX] AL MOV
AX POP 2 # SP ADD
( write the dot)
ES: AL 0 [BX] MOV
( reset video mode)
DX DEC ( 3ce) 5 # AL MOV DX AL OUT
DX INC ( 3cf) 0 # AL MOV DX AL OUT
DX DEC ( 3ce) 8 # AL MOV DX AL OUT
DX INC ( 3cf) $FF # AL MOV DX AL OUT
( pop stack into dx:bx)
BX POP
DX POP
NEXT
END-CODE
\ random number generator
VARIABLE SEED
: RANDOM ( max -- n )
SEED @ 31415927 UM* DROP 87654321 + DUP SEED ! SWAP MOD ;
VARIABLE COLOUR 15 COLOUR !
VARIABLE RX1 VARIABLE RY1 ( Real x1,y1 )
VARIABLE RX2 VARIABLE RY2 ( Real x2,y2 )
VARIABLE XQF VARIABLE YQF ( Quadrant flag--- 0:1st Q, 1:non-1st Q )
VARIABLE X1 VARIABLE Y1 ( Image x1,y1 )
VARIABLE X2 VARIABLE Y2 ( Image x2,y2 )
VARIABLE DX VARIABLE DY
VARIABLE QQ VARIABLE EE
VARIABLE K1 VARIABLE K2
: PRECAL
X1 @ RX1 ! Y1 @ RY1 ! X2 @ RX2 ! Y2 @ RY2 !
X1 @ X2 @ > IF 1 XQF ! X1 @ DUP X2 @ - + X2 ! ELSE 0 XQF ! THEN
Y1 @ Y2 @ > IF 1 YQF ! Y1 @ DUP Y2 @ - + Y2 ! ELSE 0 YQF ! THEN
X2 @ X1 @ - DX !
Y2 @ Y1 @ - DY !
DX @ DY @ / QQ !
DX @ NEGATE EE !
DY @ 2 * K1 !
DY @ QQ @ * 2 * K2 ! ;
: AFTCAL RX2 @ X1 ! RY2 @ Y1 !
0 RX1 ! 0 RY1 ! 0 X2 ! 0 Y2 ! 0 RX2 ! 0 RY2 ! 0 XQF ! 0 YQF !
0 DX ! 0 DY ! 0 QQ ! 0 EE ! 0 K1 ! 0 K2 ! ;
\ X Quadrant flag check then map onto Real if needed
: XQC ( Image x -- Real x )
XQF @ IF DUP RX1 @ - DUP + - THEN ;
\ Y Quadrant flag check then map onto Real if needed
: YQC ( Image y -- Real y )
YQF @ IF DUP RY1 @ - DUP + - THEN ;
: GXSPAN
X1 @ SWAP ABS FOR 1+ DUP COLOUR @ SWAP XQC Y1 @ YQC EGA! NEXT DROP ;
: GYSPAN
Y1 @ SWAP ABS FOR 1+ DUP COLOUR @ SWAP YQC X1 @ XQC SWAP EGA! NEXT DROP ;
: GLINE ( x y -- )
PRECAL
BEGIN
X2 @ X1 @ =
IF
Y2 @ Y1 @ - ABS GYSPAN AFTCAL EXIT
THEN
EE @ 0 <
IF COLOUR @ X1 @ XQC Y1 @ YQC EGA!
X1 @ 1+ X1 !
EE @ K1 @ + EE !
ELSE
Y1 @ 1+ Y1 !
EE @ DX @ 2 * - EE !
X1 @ QQ @ + 1- X2 @ <
IF
X1 @ QQ @ + 1- X1 @ - GXSPAN
ELSE
X2 @ X1 @ - GXSPAN
AFTCAL EXIT
THEN
X1 @ QQ @ + X1 !
EE @ K2 @ + EE !
THEN
AGAIN ;
: GMODE DONE TEXT PAGE EGA ;
: DRAW ( x y -- )
Y2 ! X2 !
X2 @ X1 @ = Y2 @ Y1 @ = AND
IF AFTCAL EXIT THEN
GLINE ;
: INIT ( -- )
0 X1 ! 0 Y1 ! AFTCAL COLOUR @ 0 0 EGA! ;
: RECTANGLE INIT 639 0 DRAW 639 349 DRAW 0 349 DRAW 0 0 DRAW ;
: ICECOLUMN ( n -- )
FOR EGA INIT
0 639 FOR 1+ DUP 100 RANDOM DRAW NEXT DROP
NEXT RECTANGLE ;
\ One second delay
: SECOND ( n -- )
FOR 200000 FOR NEXT NEXT ;
: COLORRUN ( -- )
14 FOR I 1+ COLOUR ! 0 ICECOLUMN 1 SECOND NEXT ;
: STAR ( n -- )
GMODE FOR COLOUR @ 639 RANDOM 349 RANDOM EGA! NEXT ;
表二
4 程式說明
這個程式,免不了要用到許多變數來暫存變化值,程式處理此問題時,不用節省,直接宣告想用的變數,其中凡含 X 者均與 X 方向座標有關,凡含 Y 者均與 Y 方向座標有關,硬體系統中的 ( 0 , 0 ) 點位於實際螢幕的左上角,橫軸表 X 方向,縱軸表 Y 方向。 COLOUR 變數存的是彩色螢幕上可繪出點的顏色代表值,可以由 0 到 15 。 X1 、Y1 用來存每次欲繪線時原點的座標值。 X2、 Y2 用來存欲前往之末點的座標值。 XQF 、YQF 用來存末點與原點之間的象限關係,但只是 1 或 0 的旗標值,二者均為 0 表仍在第一象限中繪圖,同理,二者均為 1 時表應該在第三象限中繪圖。 DX、DY、QQ、EE、K1、K2 則均仍延用繪線根據文獻中所用的名稱,不必解釋。
比對設計完成的程式與本文引用的原始流程圖後,可以發現有幾項重要的不同。首先是在邊界處的處理,這也是程式設計時經常會出現的通病,例如:本程式係以 X 方向為基礎,然後考慮 Y 方向的變化以決定線要如何劃,那麼當所欲前往之點,其 X 座標與原點相同,而不論 Y 是否也相同時?原流程圖就不劃而結束了,完成測試的程式對此必須安排例外處理,至少要劃一條 Y 方向的直線。其次是在劃線段的最後情況時,我們可以確定是在執行最後一段了,因此結束後程式也該終止,否則程式在回到迴路起始點 X 座標的檢查時, X 的量可能因為超出而不停止劃線,變成停不下來的劃線程式。當然,上述問題是可以透過其他方法做更好的解決,程式可以變得漂亮一些,不過,本程式強調忠實的反應被引用原文的敘述,盡量不作更動,以 FORTH 的高度彈性來配合發展,顯現出用 FORTH 發展軟體的好處。
最後,必須指出的是:原引用資料僅能在第一象限中完成工作,也就是說,當欲前往之點,其座標值 X 與 Y 均大於原點時,方法才有效。為了解決這個問題,本文自創了一個可行的方法,簡而言之,就是設法在虛像中演算,然後在實境中繪點,發展程式時,是由實際觀察執行結果,才完成這個技巧的。剛開始試將引用敘述轉換成程式時,因為只在第一象限狀況繪圖,只出現了上述的兩項邊界狀況,易於解決,隨後的測試發現其他象限都無法工作了,因此再插入增加另兩組變數宣告,使用的名稱是以 R 為字首的,如 RX1,RY1,RX2,RY2 ,此處姑且稱之為實際座標,也就是實際應該繪出點的座標,相對於此組變數,原程式的變數就成了虛座標了,但名稱是 X1,Y1,X2,Y2, 也就是被用來演算用的座標, X1,Y1 是繪圖中待繪點的座標值,會隨著演算的進行一直變化,而 X2,Y2 存的是由實境末點座標換算所得,在虛像中之末點座標值,以便維持演算計算能恆在象限 1 中進行。
虛像中演算,實境中繪點的方法,舉一由 (50,50) 繪至 (100,30) 之例,如下圖所示:
圖三
簡單敘述圖示過程為:當 (RX2,RY2) 二變數有小於 (RX1,RY1) 情況時,先求出對應於 (RX2,RY2) 的 (X2,Y2) 座標,換算原則是,必須將 RX2 或 RY2 轉換成大於 X1 或 Y1 的對應座標值 X2 或 Y2 。如上圖中欲由 (50,50) 劃至 (100,30) 時,因 RY2 之 30 小於 RY1 之 50 ,故先將旗標變數 YQF 設定成 1 ,然後將 RY2 轉換成大於 RY1 的對應座標值, Y2=30 +2*(50-30)=70。
然後仍用原法計算。
每次執行到要繪點時,檢查象限旗標變數為 1 或為 0 ?為 0 則直接繪點,為 1 則換算回實際應繪點的座標才繪點。如上圖所示,因 YQF 為 1 ,故要在實境中劃出一點的所有座標,都將由虛像中之 Y 換算回實境中的 RY ,以此圖例而言 RY=Y-2*(Y-50) ,然後才在實境中實際繪出 RY ,直到繪至 RY2 止。
所有的變數內容在完成一次繪線任務後都要歸零,只留下末點 (RX2,RY2) 的座標,改存到原點 (X1,Y1) 去當做新原點,供下一次繪線時使用,而下一個新末點的新座標要經由 (X2,Y2) 傳遞進入系統程式。
5 結論
能繪點又能繪線後的系統,功能可謂已經不少了,當初這個主題發展的目的是想將電腦螢幕設計成類似示波器的功能,然後可以利用電腦永遠不會感到疲倦,就適合用來長期監控被測試的訊號,有了這個程式就很容易達到目的了,剩下的設計,已經不太再有困難技巧值得強調。
發展程式時,為了想讓繪圖功能有較為活潑生動的觀感,另外引用了 FORTH 的一項便利功能,也就是特別容易納入亂數產生程式,宣告一個種子變數 SEED 後,再加一列程式設計,便完成了亂數來源,在應用上多麼的精簡?程式也可以動態的測試亂數性能,即使要電腦執行十萬個亂數,例如:執行 100000 STAR ,也耗不了幾秒,就可觀察到結果,相信 FORTH 愛用者的這款感受,是別種程式語言所難望其項背的。
以亂數配合繪出直線的指令,可以橫向繪出類似示波器的圖來,測試結果圖形很像是一幅冰柱圖,指令 ICECOLUMN 執行結果便是如此。
繪圖可以創作出許多生動的畫面,因此,本文的最重要意義,應該在後序的發展與應用。任何人在面對同樣問題時,一定可以發現,就僅只是要求電腦繪一條直線,不是唾手可得的時候,本文就具有高度的參考價值了。
6 另一套繪直線高階定義程式
由於前述文章引用了一個具有專利的文獻作為程式設計的根據,它簡明扼要而且執行快速,但本文僅進行學術性的探討,要進一步實際應用此法設計產品時會有侵權問題,不可如此。凡欲使用此法設計產品的讀者,請主動聯繫專利持有人。
如果欲繪的直線要求條件不高,繪線程式的演算速度要求不必嚴苛到非用低階的組合語言設計不可時,此處提供一個業經公開,沒有侵權問題的 FORTH 高階定義完整程式,足夠滿足許多場合的應用,我在數篇公開發表過的 FORTH 學術期刊中看到過這個程式,它已被許多人引用,劃線方法也叫 Bresenham 法,公益程式都不宣稱專利,可以大方使用。
另為配合 EGA 硬體顯示,我們仍須要用到在圖形顯示或文字顯示之間可以切換的操作指令 EGA 與 TEXT 。前文使用了一個 eForth32 作者 Rick VanNorman 自行設計繪出一點的低階指令 EGA! ,但此處為了增加讀者更多可茲參考的資源,另行設計一個直接呼叫作業系統內 INT $10 以完成繪點功能的精簡指令 PLOT ,可取代原用的 EGA! ,但它必須由低階組合語言來設計。程式末段則設計可直接執行的幾個示範指令,作為日後引用時參考。
另為考慮引用時的方便,此處亦為 eForth32 系統添加或重新定義指令名稱,使系統指令符合 F83 指令標準,這一修正部份,相關指令均收集於 ALIAS.4 檔案程式中。因缺乏原始文獻,無法詳述或有系統的介紹此程式繪出一直線的實質方法,但引用後驗證無誤,可以執行,我們僅能分享成果而不追究原理。程式密集列示如下:
在進行畫線技術時,我所使用的 PC XT 電腦尚無能夠方便擷取螢幕影像的軟體可用,現今唯一能找出的相關舊記錄,只剩一張當年我用此項技術開發出來的語音示波器照片,當時錄下的音譜為『大家好』。照片附貼於文末,作為紀念。
\ EGA graphics, borrowed from RIForth by Bob Illyes
\ High level line draw
LOAD ALIAS.4
CODE CRT ( n - - send a crt mode command )
BX AX MOV
$10 INT
BX POP
DX POP
NEXT
END-CODE
: EGA ( - - setup 640 by 350, 16 colour graphics mode )
16 CRT 0 20 AT ;
: TEXT ( - - setup 80 culum 24 line text mode )
3 CRT ;
: GMODE DONE TEXT PAGE EGA ;
VARIABLE COLOUR 15 COLOUR !
CODE PLOT ( x y - - ) \ 自行設計,非原作指令
CX POP
AX POP
BX DX MOV
COLOUR # DI MOV
0 [DI] AX MOV
$0C # AH MOV
0 # BX MOV
$10 INT
BX POP
DX POP
NEXT
END-CODE
\ Bresenham straight line
VARIABLE X VARIABLE Y
VARIABLE _SENSE VARIABLE SENSE
VARIABLE _DEL VARIABLE DEL
: SET ( x y - - ) Y ! X ! ; \ 起始定位指令
: RSET ( dx dy - - ) Y +! X +! ;
: DOT SWAP DUP >R 1- SWAP X @ Y @ PLOT
_DEL @ + DUP DEL @ >
IF DEL @ - _SENSE @
ELSE 0
THEN SENSE @ R> ;
: RLINE ( dx dy - - )
OVER ABS 2* OVER ABS 2* 2DUP < DUP >R
IF DEL ! _DEL ! SWAP ELSE _DEL ! DEL ! THEN
0< 2* 1+ _SENSE ! DUP 0< 2* 1+ SENSE !
DUP ABS SWAP ( count) DUP 0< NOT – ABS ( error) R>
IF BEGIN DOT WHILE RSET REPEAT ( more vertical)
ELSE BEGIN DOT WHILE SWAP RSET REPEAT ( more horizontal)
THEN 2DROP 2DROP ;
: LINE ( x y - - ) SWAP X @ - SWAP Y @ - RLINE ;
: ORIGIN ( - - ) 0 0 SET ;
: RECTANGLE ORIGIN
639 0 LINE 639 349 LINE 0 349 LINE 0 0 LINE ;
: WHITENOISE ( - - )
GMODE 0 175 SET
639 0 DO R@ 175 50 RANDOM – LINE LOOP
RECTANGLE ;
: DEMO ( - - ) \ 繪出白噪音示波器顯示式圖形
16 1 DO R@ COLOUR ! WHITENOISE
KEY? IF LEAVE THEN
2 SECONDS
LOOP ;
: STAR ( n - - ) \ 繪出指定數量的繁星圖形,可供評鑑亂數產生程式品質用
GMODE 7 COLOUR ! 0 0 SET
1 DO 639 RANDOM 349 RANDOM PLOT LOOP ;
沒有留言:
張貼留言