24/05/2018, 14:50

Các đối tượng đồ họa cơ sở

Các vùng tô là một trong những đối tượng đồ họa cơ sở được hầu hết các công cụ lập trình đồ họa hỗ trợ. Có hai dạng vùng tô thường gặp đó là : tô bằng một màu thuần nhất (solid fill) hay tô theo một mẫu tô (fill-pattern) nào đó. Một ...

Các vùng tô là một trong những đối tượng đồ họa cơ sở được hầu hết các công cụ lập trình đồ họa hỗ trợ. Có hai dạng vùng tô thường gặp đó là : tô bằng một màu thuần nhất (solid fill) hay tô theo một mẫu tô (fill-pattern) nào đó.

Một vùng tô thường được xác định bởi một đường khép kín nào đó gọi là đường biên. Một trong những dạng đường biên đơn giản nhất đó là đa giác.

Để tô màu một vùng tô, người ta thường chia làm hai công đoạn : công đoạn thứ nhất là xác định các điểm nào để tô và công đoạn còn lại đơn giản hơn đó là quyết định tô các điểm đó bằng giá trị màu nào. Công đoạn thứ hai chỉ thực sự phức tạp nếu ta tô theo một mẫu tô nào đó không phải là tô thuần một màu.

Có hai cách tiếp cận chính để tô màu một vùng tô đối với thiết bị hiển thị dạng điểm đó là : tô theo dòng quét (scan-line fill) và tô dựa theo đường biên (boundary fill).

Phương pháp tô theo dòng quét sẽ xác định các phần giao của các dòng quét kế tiếp nhau với đường biên của vùng tô, sau đó sẽ tô màu các điểm thuộc về phần giao này. Cách tiếp cận này thường được dùng để tô màu các đa giác, đường tròn, ellipse, và một số đường cong đơn giản khác. Phương pháp tô dựa theo đường biên sẽ bắt đầu từ một điểm ở bên trong vùng tô và từ đó loang dần ra cho tới khi ta gặp các điểm biên. Cách tiếp cận này thường được dùng cho các vùng tô có dạng đường biên phức tạp hơn.

Thuật toán tô màu dựa theo dòng quét

Giả sử vùng tô được cho bởi một đa giác N đỉnh : . Đa giác này có thể là đa giác lồi, đa giác lõm, và cả đa giác tự cắt, …

Hình 2.18 sau minh họa ý tưởng chính của thuật toán. Với mỗi dòng quét, ta sẽ xác định phần giao của đa giác và dòng quét rồi tô màu các pixel thuộc đoạn giao đó. Để xác định các đoạn giao ta tiến hành việc tìm giao điểm của dòng quét với các cạnh của đa giác, sau đó các giao điểm này sẽ được sắp theo thứ tự tăng dần của hoành độ giao điểm. Các đoạn giao chính là các đoạn thẳng được giới hạn bởi từng cặp giao điểm một, ví dụ như (0,1), (2,3), ….

Hình 2.18 – Thuật toán scan-line với một dòng quét nào đó

Ta có thể tóm bắt các bước chính của thuật toán :

  • Tìm , lần lượt là giá trị lớn nhất, nhỏ nhất của tập các tung độ của các đỉnh của đa giác đã cho , .
  • Ứng với mỗi dòng quét , với k thay đổi từ đến , lặp :
  • Tìm tất cả các hoành độ giao điểm của dòng quét với các cạnh của đa giác.
  • Sắp xếp các hoành độ giao điểm theo thứ tự tăng dần : x0, x1, …
  • Tô màu các đoạn thẳng trên đường thẳng lần lượt được giới hạn bởi các cặp .

Nếu chỉ dừng ở mức này và chuyển sang cài đặt, chúng ta sẽ gặp một số vấn đề sau :

  • Nhận xét rằng, ứng với mỗi dòng quét, không phải lúc nào tất cả các cạnh của đa giác cũng tham gia cắt dòng quét. Do đó để cải thiện tốc độ cần phải có một cách nào đó để hạn chế được số cạnh cần tìm giao điểm ứng với mỗi dòng quét.
  • Việc tìm giao điểm của cạnh đa giác với mỗi dòng quét sẽ gặp các phép toán phức tạp như nhân, chia, … trên số thực nếu ta dùng cách giải hệ phương trình tìm giao điểm. Điều này sẽ làm giảm tốc độ thuật toán khi phải lặp đi lặp lại nhiều lần thao tác này khi dòng quét quét qua đa giác.
  • Nếu số giao điểm tìm được giữa các cạnh đa giác và dòng quét là lẻ thì việc nhóm từng cặp giao điểm kế tiếp nhau để hình thành các đoạn tô có thể sẽ không chính xác. Điều này chỉ xảy ra khi dòng quét đi ngang qua các đỉnh của đa giác. Nếu tính số giao điểm tại đỉnh dòng quét đi ngang qua là hai thì có thể sẽ cho kết quả tô không chính xác như trong trường hợp của hình 2.19. Ngoài ra, việc tìm giao điểm của dòng quét với các cạnh nằm ngang là một trường hợp đặc biệt cần phải có cách xử lí thích hợp.

Để giải quyết các vấn đề trên, cần phải xây dựng một cấu trúc dữ liệu và thuật toán thích hợp đối với chúng.

Hình 2.19 – Dòng quét y=k2 đi ngang qua đỉnh có thể sẽ cho kết quả tô không chính xác so với dòng quét y=k1

Danh sách các cạnh kích hoạt AET (Active Edge Table)

Để hạn chế số cạnh cần tìm giao điểm ứng với mỗi dòng quét, ta xây dựng một số cấu trúc dữ liệu như sau :

Cạnh đa giác (EDGE)

Mỗi cạnh của đa giác được xây dựng từ hai đỉnh kề nhau gồm các thông tin sau :

  • : giá trị tung độ nhỏ nhất trong 2 đỉnh của cạnh.
  • xIntersect : hoành độ giao điểm của cạnh với dòng quét hiện đang xét.
  • DxPerScan : giá trị 1/m (m là hệ số góc của cạnh).
  • deltaY : khoảng cách từ dòng quét hiện hành tới đỉnh .

Danh sách các cạnh kích hoạt AET

Danh sách này dùng để lưu các tập cạnh của đa giác có thể cắt ứng với dòng quét hiện hành và tập các điểm giao tương ứng. Nó có một số đặc điểm :

  • Các cạnh trong danh sách được sắp theo thứ tự tăng dần của các hoành độ giao điểm để có thể tô màu các đoạn giao một cách dễ dàng.
  • Thay đổi ứng với mỗi dòng quét đang xét, do đó danh sách này sẽ được cập nhật liên tục trong quá trình thực hiện thuật toán. Để hỗ trợ cho thao tác này, đầu tiên người ta sẽ tổ chức một danh sách chứa toàn bộ các cạnh của đa giác gọi là ET (Edge Table) được sắp theo thứ tự tăng dần của , rồi sau mỗi lần dòng quét thay đổi sẽ di chuyển các cạnh trong ET thỏa điều kiện sang AET.
  • Một dòng quét chỉ cắt một cạnh của đa giác khi và chỉ khi . Chính vì vậy mà với cách tổ chức của ET (sắp theo thứ tự tăng dần của ) điều kiện để chuyển các cạnh từ ET sang AET sẽ là ; và điều kiện để loại một cạnh ra khỏi AET là .

Hình 2.20 – Thông tin của một cạnh

Công thức tìm giao điểm nhanh

Nếu gọi , lần lượt là các hoành độ giao điểm của một cạnh nào đó với các dòng quét , ta có :

hay .

Như vậy nếu lưu hoành độ giao điểm ứng với dòng quét trước lại, cùng với hệ số góc của cạnh, ta có thể dễ dàng xác định hoành độ giao điểm ứng với dòng quét kế tiếp một cách đơn giản theo công thức trên. Điều này rút gọn đáng kể thao tác tìm giao điểm của cạnh ứng với dòng quét. Chính vì vậy mà trong thông tin của một cạnh chúng ta có hai biến DxPerScan và xIntersect.

Hình 2.21 – Công thức tìm giao điểm nhanh

Giải quyết trường hợp dòng quét đi ngang qua đỉnh

Người ta đưa ra quy tắc sau để tính số giao điểm khi dòng quét đi ngang qua đỉnh :

  • Tính một giao điểm nếu chiều của hai cạnh kề của đỉnh đó có xu hướng tăng hay giảm.
  • Tính hai giao điểm nếu chiều của hai cạnh kề của đỉnh đó có xu hướng thay đổi, nghĩa là tăng-giảm hay giảm-tăng.

Hình 2.22 – Quy tắc tính một giao điểm (a) và hai giao điểm (b)

Khi cài đặt để khỏi phải xét điều kiện này cho phức tạp, khi xây dựng dữ liệu cho mỗi cạnh trước khi đưa vào ET, người ta sẽ xử lí các cạnh có đỉnh tính hai giao điểm bằng cách loại đi một pixel trên cùng của một trong hai cạnh như hình 2.23 :

Hình 2.23 – Cạnh được lưu trong ET chỉ là

Cài đặt minh họa sau sử dụng chung một danh sách EDGELIST cho cả ET và AET. AET được quản lí nhờ vào hai con trỏ FirstId và LastId.

Cài đặt minh họa thuật toán tô màu scan-line

#include<stdio.h>

#include<conio.h>

#include<stdlib.h>

#include<graphics.h>

#include<dos.h>

#define MAXVERTEX 20

#define MAXEDGE 20

#define TRUE 1

#define FALSE 0

typedef struct {

int x;

int y;

}POINT;

typedef struct{

int NumVertex;

POINT aVertex[MAXVERTEX];

}POLYGON;

typedef struct {

int NumPt;

float xPt[MAXEDGE];

}XINTERSECT;

typedef struct

{

int yMin;// Gia tri y nho nhat cua 2 dinh

float xIntersect;// Hoanh do giao diem cua canh & dong quet

float dxPerScan;// Gia tri 1/m

int DeltaY;

}EDGE;

typedef struct

{

int NumEdge;

EDGE aEdge[MAXEDGE];

}EDGELIST;

/*

Dat 1 canh vao danh sach canh.

Cac canh duoc sap theo thu tu giam dan cua yMin (yMin la gia tri y lon nhat cua 2 dinh 1 canh)

Xu li luon truong hop dong quet di ngang qua dinh ma tai do chi tinh 1 diem giao

*/

voidPutEdgeInList(EDGELIST &EdgeList, POINT p1, POINT p2,

int NextY)

{

EDGE EdgeTmp;

EdgeTmp.dxPerScan =float(p2.x-p1.x)/(p2.y-p1.y);// 1/m

if(p1.y < p2.y)

{

/*

Truong hop dong quet di ngang qua dinh la giao diem

cua 2 canh co huong y cung tang

*/

if(p2.y < NextY)

{

p2.y--;

p2.x -= EdgeTmp.dxPerScan;

}

EdgeTmp.yMin = p1.y;

EdgeTmp.xIntersect= p1.x;

EdgeTmp.DeltaY = abs(p2.y-p1.y)+1;

} // if

else

{

/*

Truong hop dong quet di ngang qua dinh la giao diem cua 2 canh co huong y cung giam

*/

if(p2.y > NextY)

{

p2.y++;

p2.x+= EdgeTmp.dxPerScan;

}

EdgeTmp.yMin = p2.y;

EdgeTmp.xIntersect= p2.x;

EdgeTmp.DeltaY = abs(p2.y-p1.y)+1;

}//else

// xac dinh vi tri chen

int j = EdgeList.NumEdge;

while((j>0)&&(EdgeList.aEdge[j-1].yMin>EdgeTmp.yMin))

{

EdgeList.aEdge[j]= EdgeList.aEdge[j-1];

j--;

}

// tien hanh chen dinh moi vao canh

EdgeList.NumEdge++;

EdgeList.aEdge[j]= EdgeTmp;

} // PutEdgeInList

/*

Tim dinh ke tiep sao cho khong nam tren cung duong thang voi dinh dang xet

*/

intFindNextY(POLYGON P,int id)

{

int j =(id+1)%P.NumVertex;

while((j<P.NumVertex)&&(P.aVertex[id].y == P.aVertex[j].y))

j++;

if(j<P.NumVertex)

return(P.aVertex[j].y);

return 0;

} // FindNextY

// Tao danh sach cac canh tu polygon da cho

voidMakeSortedEdge(POLYGON P, EDGELIST &EdgeList,

int&TopScan,int&BottomScan)

{

TopScan = BottomScan = P.aVertex[0].y;

EdgeList.NumEdge = 0;

for(int i=0; i<P.NumVertex; i++)

{

// Truong hop canh khong phai la canh nam ngang if(P.aVertex[i].y != P.aVertex[i+1].y)

PutEdgeInList(EdgeList, P.aVertex[i], P.aVertex[i+1], FindNextY(P, i+1));

//else Xu li truong hop canh nam ngang

if(P.aVertex[i+1].y > TopScan)

TopScan = P.aVertex[i+1].y;

}

BottomScan = EdgeList.aEdge[0].yMin;

} //MakeSortedEdge

// Cap nhat lai hai con tro FirstId, LastId cho biet danhsach cac canh active

voidUpdateActiveEdgeList(EDGELIST EdgeList,int yScan,int&FirstId,int&LastId)

{

while((FirstId<EdgeList.NumEdge-1) &&(EdgeList.aEdge[FirstId].DeltaY == 0))

FirstId++;

while((LastId<EdgeList.NumEdge-1)&&(EdgeList.aEdge[LastId+1].yMin<=yScan))

LastId++;

} // UpdateActiveEdgeList

voidSortOnX(XINTERSECT & aIntersectPt)

{

for(int i=0; i<aIntersectPt.NumPt-1; i++)

{

int Min = i, t;

for(int j=i+1; j<aIntersectPt.NumPt; j++)

if( aIntersectPt.xPt[j]< aIntersectPt.xPt[Min])

Min = j;

t = aIntersectPt.xPt[Min];

aIntersectPt.xPt[Min]= aIntersectPt.xPt[i];

aIntersectPt.xPt[i]= t;

}

} // SortOnX

/*

Tim cac hoanh do giao diem cua cac canh cua da giac voi dong quet yScan. Sau khi tim cac hoanh do giao diem, ta sap xep lai theo chieu tang cua x

*/

voidFindXIntersection(EDGELIST EdgeList, XINTERSECT &aIntersectPt,int FirstId,int LastId)

{

aIntersectPt.NumPt = 0;

for(int i=FirstId; i<=LastId; i++)

{

if(EdgeList.aEdge[i].DeltaY>0)

{

aIntersectPt.xPt[aIntersectPt.NumPt]= EdgeList.aEdge[i].xIntersect;

aIntersectPt.NumPt++;

}

}

SortOnX(aIntersectPt);

} //FindXIntersection

#define Round(x)int(x+0.5)

voidFillLine(XINTERSECT aIntersectPt,int yScan)

{

for(int i=0; i<aIntersectPt.NumPt; i+=2)

line(Round(aIntersectPt.xPt[i]), yScan, Round(aIntersectPt.xPt[i+1]), yScan);

} // FillLine

voidUpdateEdgeList(EDGELIST &EdgeList,int FirstId,int LastId)

{

for(int i=FirstId; i<=LastId; i++)

{

if(EdgeList.aEdge[i].DeltaY>0)

{

EdgeList.aEdge[i].DeltaY--;

EdgeList.aEdge[i].xIntersect += EdgeList.aEdge[i].dxPerScan;

}

}

} //FillLine

void ScanLineFill(POLYGON P)

{

EDGELIST EdgeList;

XINTERSECT aIntersectPt;

int TopScan, BottomScan, FirstId, LastId;

MakeSortedEdge(P, EdgeList, TopScan, BottomScan);

FirstId = LastId = 0;

for(int i=BottomScan; i<=TopScan; i++)

{

// Cap nhat lai danh sach cac canh active - tuc la cac canh cat dong quet i

UpdateActiveEdgeList(EdgeList, i, FirstId, LastId);

// Tim cac hoanh do giao diem cua dong quet voi cac canh cua da giac va sap xep lai cac hoanh do giao diem truc tiep tren EdgeList

FindXIntersection(EdgeList, aIntersectPt, FirstId, LastId);

FillLine(aIntersectPt, i);

UpdateEdgeList(EdgeList, FirstId, LastId);

}

} //ScanLineFill

Lưu đồ thuật toán tô màu theo dòng quét

Thuật toán tô màu dựa theo đường biên

Khác với thuật toán tô màu dựa theo dòng quét, đường biên của vùng tô được xác định bởi tập các đỉnh của một đa giác, đường biên trong thuật toán được mô tả bằng một giá trị duy nhất đó là màu của tất cả các điểm thuộc về đường biên.

Bắt đầu từ điểm nằm bên trong vùng tô, ta sẽ kiểm tra các điểm lân cận của nó đã được tô màu hay có phải là điểm biên hay không, nếu không phải là điểm đã tô và không phải là điểm biên ta sẽ tô màu nó. Quá trình này được lặp lại cho tới khi nào không còn tô được điểm nào nữa thì dừng. Bằng cách này, toàn bộ các điểm thuộc vùng tô được kiểm tra và sẽ được tô hết.

Hình 2.24 – Thuật toán tô màu dựa theo đường biên

Có hai quan điểm về cách tô này, đó là dùng bốn điểm lân cận hay tám điểm lân cận đối với điểm đang xét được tô bằng màu trắng (xem hình 2.25).

Hình 2.25 – 4 điểm lân cận (a) và 8 điểm lân cận (b)

Đoạn chương trình sau minh họa cài đặt thuật toán tô màu dựa theo đường biên sử dụng phương pháp tô 4 điểm lân cận.

Cài đặt minh họa thuật toán tô màu dựa theo đường biên

voidBoundaryFill(int x,int y,int FillColor,int BoundaryColor)

{

int CurrentColor;

CurrentColor = getpixel(x,y);

if((CurrentColor!=BoundaryColor)&&CurrentColor!= FillColor))

{

putpixel(x,y,FillColor);

BoundaryFill(x-1, y, FillColor, BoundaryColor);

BoundaryFill(x, y+1, FillColor, BoundaryColor);

BoundaryFill(x+1, y, FillColor, BoundaryColor);

BoundaryFill(x, y-1, FillColor, BoundaryColor);

}

} // Boundary Fill

Nhận xét

  • Thuật toán này có thể sẽ không hoạt động chính xác khi có một số điểm nằm trong vùng tô có màu là màu cần tô của vùng (FillColor). Để khắc phục điều này, trước khi tô màu cần phải đảm bảo rằng toàn bộ các điểm thuộc về vùng tô có màu khác màu tô.
  • Nhận xét rằng trong cài đặt thuật toán ở trên, việc gọi thực hiện đệ quy thuật toán cho bốn điểm lân cận của điểm hiện hành không quan tâm tới một trong bốn điểm đó đã được xét ở bước trước hay chưa. Ví dụ khi ta xét bốn điểm lân cận của điểm hiện hành (x,y), thì khi gọi thực hiện đệ quy với điểm hiện hành là một trong bốn điểm lân cận trên, (x,y) vẫn được xem là điểm lân cận của chúng và lại được gọi thực hiện lại. Ta sẽ đưa ra một cải tiến nhỏ để khắc phục điểm này, bằng cách mỗi lần xét điểm hiện hành (x,y) ta sẽ gọi 4 thủ tục riêng để tô các điểm lân cận và trong 4 thủ tục này ta sẽ tránh gọi lại việc xét điểm (x,y).

voidBoundaryFillEnhanced(int x,int y,int F_Color,int B_Color)

{

int CurrentColor;

CurrentColor = getpixel(x,y);

if((CurrentColor!=B_Color)&&CurrentColor!= F_Color))

{

putpixel(x,y,F_Color);

FillLeft(x-1, y, F_Color, B_Color);

FillTop(x, y+1, F_Color, B_Color);

FillRight(x+1, y, F_Color, B_Color);

FillBottom(x, y-1, F_Color, B_Color);

}

} // BoundaryFillEnhanced

voidFillLeft(int x,int y,int F_Color,int B_Color)

{

int CurrentColor;

CurrentColor = getpixel(x,y);

if((CurrentColor!=B_Color)&&CurrentColor!= F_Color))

{

putpixel(x,y,F_Color);

FillLeft(x-1, y, F_Color, B_Color);

FillTop(x, y+1, F_Color, B_Color);

FillBottom(x, y-1, F_Color, B_Color);

}

} // FillLeft

voidFillTop(int x,int y,int F_Color,int B_Color)

{

int CurrentColor;

CurrentColor = getpixel(x,y);

if((CurrentColor!=B_Color)&&CurrentColor!= F_Color))

{

putpixel(x,y,F_Color);

FillLeft(x-1, y, F_Color, B_Color);

FillTop(x, y+1, F_Color, B_Color);

FillRight(x+1, y, F_Color, B_Color);

}

} // FillTop

voidFillRight(int x,int y,int F_Color,int B_Color)

{

int CurrentColor;

CurrentColor = getpixel(x,y);

if((CurrentColor!=B_Color)&&CurrentColor!= F_Color))

{

putpixel(x,y,F_Color);

FillTop(x, y+1, F_Color, B_Color);

FillRight(x+1, y, F_Color, B_Color);

FillBottom(x, y-1, F_Color, B_Color);

}

} // FillRight

voidFillBottom(int x,int y,int F_Color,int B_Color)

{

int CurrentColor;

CurrentColor = getpixel(x,y);

if((CurrentColor!=B_Color)&&CurrentColor!= F_Color))

{

putpixel(x,y,F_Color);

FillLeft(x-1, y, F_Color, B_Color);

FillRight(x+1, y, F_Color, B_Color);

FillBottom(x, y-1, F_Color, B_Color);

}

} // FillBottom

Thuật toán này có tính đệ quy, do đó khi cài đặt thường gây lỗi tràn bộ nhớ khi vùng tô khá lớn, do đó để cải tiến chúng ta sẽ tiến hành loang dần và lần lượt tô từng đoạn giao theo dòng quét ngang thay vì tô theo 4 điểm lân cận. Như vậy chúng ta chỉ cần lưu lại thông tin của điểm bắt đầu mỗi đoạn giao của dòng quét ngang thay vì phải lưu hết tất cả các điểm lân cận chưa được tô của điểm hiện hành. Chúng ta sẽ cho các dòng quét loang từ điểm bắt đầu theo hướng lên biên trên, sau khi đã tô xong, các dòng quét còn lại theo hướng xuống biên dưới sẽ được tô. Ứng với mỗi dòng quét ngang, ta sẽ loang và tìm pixel trái nhất (có hoành độ nhỏ nhất) để lưu lại. Trong hình 2.26, đoạn giao đầu tiên chứa điểm bắt đầu (tô màu trắng) sẽ được tô trước. Sau đó các vị trí 1, 2 ứng với các đoạn giao của các dòng quét kế tiếp sẽ được lưu lại (hình 2.26a). Bước tiếp theo, điểm ứng với vị trí 2 sẽ được lấy ra và tiến hành tô màu bằng cách loang từ điểm này ra theo chiều ngang, sau đó pixel ứng vị trí 3 của dòng quét kế tiếp sẽ được lưu lại (hình 2.26b). Sau khi dòng quét ứng với điểm 3 đã được xử lí tương tự như trên xong, stack lưu các vị trí của các điểm “hạt giống” cho các dòng quét kế tiếp như trong hình 2.26c. Hình 2.26d minh họa khi thuật toán đã tô được toàn bộ một phần vùng phía trên bên phải của vùng tô. Khi pixel ứng với vị trí 5 được xử lí xong, ta có phần còn lại phía trên bên trái sẽ được tô. Sau đó pixel ứng với vị trí 4 sẽ được xử lí, các dòng quét phía dưới sẽ được tô tiếp theo.

Hình 2.26 – Thuật toán tô màu theo dòng quét cải tiến

cung cấp các công cụ cơ bản nhất cho việc xây dựng các ảnh đồ họa của các đối tượng phức tạp. Các đoạn thẳng, đường cong, vùng tô, kí tự, … là các đối tượng đồ họa cơ sở được hầu hết tất cả các công cụ lập trình đồ họa hỗ trợ.

Mỗi đối tượng đồ họa cơ sở được mô tả thông qua dữ liệu về tọa độ và các thuộc tính của nó. Hệ tọa độ thường được dùng để mô tả đối tượng là hệ tọa độ Descartes. Các thuộc tính của đối tượng như màu sắc, kiểu, độ rộng, … cho biết kiểu cách mà đối tượng được hiển thị trên thiết bị.

Để có thể hiển thị các đối tượng đồ họa trên thiết bị hiển thị dạng điểm mà điển hình là màn hình, cần phải có một quá trình chuyển các mô tả hình học của các đối tượng này trong hệ tọa độ thế giới thực về dãy các pixel tương ứng gần với chúng nhất trên hệ tọa độ của thiết bị. Quá trình này còn được gọi là quá trình chuyển đổi bằng dòng quét. Yêu cầu quan trọng nhất đối với quá trình này ngoài việc phải cho kết quả xấp xỉ tốt nhất còn phải cho tốc độ tối ưu.

Ba cách tiếp cận để vẽ đoạn thẳng gồm thuật toán DDA, thuật toán Bresenham, thuật toán MidPoint đều tập trung vào việc đưa ra cách chọn một trong hai điểm nguyên kế tiếp khi đã biết được điểm nguyên ở bước trước. Thuật toán DDA đơn giản chỉ dùng thao tác làm tròn nên phải dùng các phép toán trên số thực, trong khi đó thuật toán Bresenham và thuật toán MidPoint đưa ra cách chọn phức tạp hơn nhưng cho kết quả tốt hơn. Đối với trường hợp vẽ đoạn thẳng, hai thuật toán Bresenham và thuật toán MidPoint cho kết quả giống nhau và tốc độ tối ưu.

Các đối tượng khác như đường tròn, ellipse và các đường conics khác cũng được vẽ tương tự bằng cách sử dụng thuật toán MidPoint. Riêng với các đường phức tạp hơn như đường spline, sẽ được xây dựng từ các đoạn thẳng xấp xỉ với đường cong thay vì phải xấp xỉ chúng từ các điểm (xem phần sau).

Các thuật toán tô màu các vùng tô thường chia làm hai công đoạn : công đoạn thứ nhất là xác định các điểm nào để tô và công đoạn còn lại đơn giản hơn đó là quyết định tô các điểm đó bằng giá trị màu nào. Công đoạn thứ hai chỉ thực sự phức tạp nếu ta tô theo một mẫu tô nào đó không phải là tô thuần một màu. Có hai cách tiếp cận chính để tô màu một vùng tô đối với thiết bị hiển thị dạng điểm đó là : tô theo dòng quét và tô dựa theo đường biên. Cách tô theo dòng quét thường được dùng để tô màu các đa giác, đường tròn, ellipse, và một số đường cong đơn giản khác, còn cách tô theo đường biên thường được dùng cho các vùng tô có dạng đường biên phức tạp hơn.

Thuật toán tô màu đa giác theo dòng quét xác định các điểm thuộc vùng tô bằng cách xác định phần giao của các dòng quét với các đoạn thẳng biên của đa giác. Điểm độc đáo của thuật toán này ở chỗ đưa ra cấu trúc dữ liệu danh sách các cạnh kích hoạt AET và cách hoạt động của chúng để có thể hạn chế tối đa các cạnh cần tìm giao điểm ứng với mỗi dòng quét. Đây là điểm mấu chốt trong vấn đề cải thiện tốc độ của thuật toán. Thuật toán này có thể được áp dụng cho nhiều dạng đa giác khác nhau như đa giác lồi, đa giác lõm, và cả đa giác tự cắt, …

Thuật toán tô màu dựa theo đường biên xuất phát từ điểm nằm bên trong vùng tô và tiến hành loang dần ra các điểm lân cận cho tới khi gặp các điểm thuộc biên thì dừng. Cách làm này gặp hạn chế về bộ nhớ khi cài đặt bằng đệ quy. Một phương pháp cải tiến đã được đề cập đó là loang theo từng dòng quét. Việc tô màu theo cách này thực sự là thuận tiện cho các chương trình đồ họa ứng dụng có khả năng tương tác cao.

1.Thiết kế và cài đặt hàm vẽ hình chữ nhật, đường gấp khúc, đa giác từ hàm vẽ đoạn thẳng.

2.Trong phần trình bày thuật toán Bresenham để vẽ đường thẳng, hãy cho biết với cách đặt d1, d2 như vậy, có khi nào d1, d2 lấy giá trị âm hay không ? Nếu có hãy cho ví dụ minh họa.

3.Tại sao phải so sánh với giá trị 0 trong các thuật toán Bresenham, MidPoint. Bản chất của việc so sánh này là gì?

4.Cài đặt các thuật toán DDA, Bresenham, MidPoint vẽ đoạn thẳng qua hai điểm cho trước trong trường hợp tổng quát với hệ số góc m lấy giá trị bất kì.

5.Người ta có thể cải thiện tốc độ cài đặt thuật toán vẽ đoạn thẳng bằng cách chỉ cần vẽ một nửa đoạn thẳng, phần còn lại lấy đối xứng nửa đoạn thẳng đã vẽ. Hãy cài đặt minh họa.

6.Cho biết các điểm nguyên vẽ phát sinh khi sử dụng các thuật toán DDA, MidPoint cho các đoạn thẳng đi qua các điểm lần lượt là A1(5,10), B1(15,17); A2(-2,3), B2(-12,7); A3(6,3), B3(9,13); A4(2,4), B4(-5,14); A5(0,10), B5(15,10); A6(5,-1), B6(5,-11);

7.Trình bày thuật toán MidPoint vẽ cung tròn 1/8, bán kính R, tâm I(xC, yC) và được giới hạn bởi :

R*(sqrt(2)/2) <= x <= R

0 <= y <= R*(sqrt(2)/2)

8.Sử dụng ý tưởng của thuật toán Bresenham, xây dựng thuật toán vẽ đường tròn có tâm là gốc tọa độ, bán kính R.

9.Giải thích tại sao chỉ chọn cung 1/8 để vẽ rồi lấy đối xứng mà không mở rộng cho cung 1/16 hay 1/32.

10.Giải thích tại sao có thể thay công thức p0 = 5/4 - R bằng công thức p0 = 1- R khi cài đặt thuật toán MidPoint vẽ đường tròn.

11.Trình bày thuật toán Bresenham vẽ đường tròn bán kính R, từ đó nhận xét về cách tiếp cận của thuật toán MidPoint có gì lợi hơn so với thuật toán Bresenham.

12.Xây dựng và cài đặt thuật toán vẽ ellipse có tâm là gốc tọa độ với bán kính trục chính, bán kính trục phụ lần lượt là A, B.

13. Dựa vào thuật toán vẽ đường tròn để xây dựng thủ tục vẽ một cung tròn (arc) tâm (x,y) bán kính R, biết góc bắt đầu và kết thúc của cung lần lượt là a , b ..

14. Dựa vào thuật toán vẽ ellipse để xây dựng thủ tục vẽ một cung (pie slice) tâm (x,y) và bán kính trục chính, trục phụ lần lượt là A, B, góc bắt đầu và kết thúc của cung lần lượt là a , b .

15. Hãy tìm hiểu các cài đặt tối ưu hơn cho các thuật toán vẽ đoạn thẳng và vẽ đường tròn, ellipse.

16. Xây dựng và cài đặt thuật toán vẽ các parabol , và với A là số nguyên bất kì.

17. Xây dựng và cài đặt thuật toán vẽ các hyperbol , và với A, B là các số nguyên bất kì.

18. Xây dựng và cài đặt thuật toán vẽ các đường cong sau :

, với A nguyên.

, với A nguyên.

, với A nguyên.

, với A nguyên.

19. Các bước chính của các thuật toán vẽ đường dạng . Minh họa cho các trường hợp vẽ đường thẳng, đường tròn.

20. Bản chất của quá trình vẽ các đường đơn giản theo từng điểm là rời rạc hóa và nguyên hóa. Hãy cho biết lí do tại sao, bước nào trong thuật toán tổng quát thể hiện hai ý trên. Minh họa bằng các đường đã học.

21. Các thuật toán vẽ đường bao hàm rất lớn kĩ thuật tối ưu hóa chương trình. Hãy minh họa qua các đối tượng đã học.

22. Ý nghĩa của danh sách kích hoạt AET trong thuật toán tô màu đa giác theo dòng quét. Cấu trúc dữ liệu và nguyên tắc hoạt động của AET.

23. Cài đặt thuật toán tô màu đa giác theo dòng quét bằng cách dùng xâu liên kết thay vì dùng mảng như cài đặt minh họa.

24. Cài đặt thuật toán tô màu theo đường biên không dùng đệ quy.

25. Xây dựng và cài đặt thuật tô màu đường tròn, ellipse.

0