7.16. Построковий алгоритм заповнення із затравкою

 

Як видно з обох прикладів, стек може стати досить великим. Ще один недолік попереднього алгоритму − стек найчастіше містить дублюючу або непотрібну інформацію. В порядковому алгоритмі заповнення із затравкою розмір стека мінімізується за рахунок зберігання лише одного затравочного пікселя  для будь-якого безперервного інтервалу на скануючому рядку. Неперервний інтервал − це група приєднаних один до одного пікселів (обмежена вже заповненими або граничними пікселями). Для розробки алгоритму використовуємо евристичний підхід, проте також можливий і теоретичний підхід, що заснований на теорії графів.

 

 

Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: 2

Рис. 7.33. Затравне заповнення області з порожниною за допомогою простого стекового алгоритму

 

Даний алгоритм застосуємо до гранично-визначених областей. Гранично-визначена 4-зв'язна область може бути як опуклою, так і не опуклою, а також може містити порожнини.

В області, зовнішньої і прилеглої до нашої гранично-визначеної області, не повинно бути пікселів з кольором, яким область або багатокутник заповнюється. Схематично роботу алгоритму можна розбити на чотири етапи.

Порядковий алгоритм заповнення із затравкою  − затравний піксель на інтервалі витягується зі стека, що містять затравні пікселі.

Інтервал із затравним піксельом заповнюється вліво і вправо від затравки вздовж скануючого рядка до тих пір, поки не буде знайдена межа.

У змінних Xлів і Хправ запам'ятовуються крайній лівий і крайній правий піксель інтервалу.

У діапазоні Xлів ≤ х ≤ Хправ. перевіряються рядки, що розміщуються безпосередньо над і під поточним рядком. Визначається, чи є на них ще не заповнені пікселі. Якщо пікселі є (тобто, не всі пікселі граничні або вже занаповнені), то в зазначеному діапазоні крайній правий піксель в кожному інтервалі відзначається як затравний і поміщається в стек.

При ініціалізації алгоритму в стек поміщається єдиний затравний піксель, робота завершується при очищенні стека. Як наведено на рис. 7.34 і в прикладі нижче, алгоритм справляється з порожнинами та зубцями на межі. Нижче наводиться більш докладний опис алгоритму на псевдокоді.

Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: Описание: 2

Рис. 7.34. Порядковий алгоритм заповнення із затравкою для багатокутника

 

Построковий алгоритм заповнення із затравкою

Затравка (х, у) видає затравний піксель

Pop − процедура, яка витягує піксель зі стека.

Push − процедура, яка поміщає піксель в стек

Ініціалізуєм стек

Push Затравка (х, у)

while (стек не порожній)

витягуємо піксель зі стека і присвоюємо йому нове значення

Pop Піксель (х, у)

Піксель (х, у) = Нов значення

зберігаємо х-координату затравного пікселя

Час___х = х

заповнюємо інтервал праворуч від затравки

X = X + 1

while Піксель (х, у) <> Гран___ значення

Піксель (х, у) = Нов___значення

х = х + 1

end while

зберігаємо крайній праворуч піксель

Х-прав = х - 1

відновлюємо х-координату затравки

х = Час х

заповнюємо інтервал ліворуч від затравки

х = х - 1

While Піксель (х, у) <> Граничні значення

Піксель (х, у) = Нові значення

X = X - 1

end while

зберігаємо крайній ліворуч піксель

Хлів = х + 1

відновлюємо х-координату затравки

х = Час х

перевіримо, чи рядок вище не є ні межею багатокутника, ні вже повністю заповненим; якщо це не так, то знайти затравку, починаючи з лівого краю підінтервалу скануючого рядка

 х = Хлів

у = у + 1

while х <Хправ

шукаємо затравку на рядку вище

Флажок = 0

while (Піксель (х, у) <> Гран___значення and

 Піксель (х, у) <> Нов___значення and x <Хправ

  if Флажок = 0 then Флажок = 1

X = X + 1

end while

поміщаємо в стек крайній праворуч піксель

if Флажок = 1 then

if (x = Хправ and Піксель (х, у) < > Гран_значення and Піксель (х, у) < > Нов_значення) then

Push Піксель (х, у)

else 

Push Піксель (х - 1, у)

end if

Флажок = 0

end if 

продовжимо перевірку, якщо інтервал був перерваний

Хвхід = х

while ((Піксель (х, у) = Гран_значення or Піксель (х, у) =

= Нов значення) and x <Хправ)

X = X + 1

end while 

переконаємось, що координата пікселя збільшена

If х = Хвхід then х = х + 1

end while

перевіримо, чи рядок нижче не є ні межею багатокутника, ні вже повністю заповненою

ця частина алгоритму абсолютно аналогічна перевірці для рядка вище, за винятком того, що замість у = у + 1 потрібно підставити у = у - 1

end while

finish

 

Тут функція Pop витягує координати х,у пікселя зі стека, а функція Push поміщає їх в стек.

 

Приклад 7.12. Построковий алгоритм заповнення із затравкою.

Розглянемо роботу алгоритму для гранично-визначеної області наведеної  на рис. 7.34. При ініціалізації в стек поміщається затравний піксель, позначений як затравка (5,7) на рис. 7.34,а. Спочатку в якості затравки інтервалу зі стека витягується цей піксель. Інтервал заповнюється праворуч і ліворуч від затравки. Знайдені при цьому кінці інтервал Xправ = 9 та Хлів = 1. Тоді перевіряється строка, що розміщена вище поточної і виявляється, що вона не гранична і не заповнена. Крайнім правим піксельом в діапазоні 1 ≤ х ≤ 9 виявляється піксель (8,8), позначений цифрою 1 на рис. 7.34,а. Цей піксель потрапляє у стек. Тоді аналогічним чином обробляється рядок нижче поточного. У діапазоні Хправ є два підінтервали на цьому рядку. Для лівого інтервалу в якості затравки виступає піксель (3,6), що позначений цифрою 2 на рис. 7.34,а, він теж поміщається  в стек. Затравка для правого підінтервала − піксель (9,6), він потрапляє у стек. Зауважимо, що піксель (9,6) − це не самий крайній правий піксель на інтервалі, однак це самий крайній правий піксель в діапазоні Хлів ≤ х ≤ Хправ, тобто, в діапазоні 1 ≤ х ≤ 9. На цьому завершується перший прохід алгоритму.

Далі зі стека витягується верхній піксель. Тут заповнюються інтервали, розміщені в правій частині багатокутника на послідовних скануючих рядках. Для рядка 3 затравкою служить піксель (10, 3) (рис. 7.34, d). У результаті заповнення інтервалу праворуч і ліворуч від затравки отримуємо нові межі діапазону: Хлів = 1 та Xправ = 10. Обробляючи рядок вище, отримуємо для лівого підінтервалу затравний піксель (3,4), який потрапляє у стек. Правий підінтервал до цього часу вже заповнений. Обробка рядка нижче дає затравку (3,2) для лівого і (10,2) для правого підінтервалів. Ці пікселі також помішаються в стек. Саме зараз досягається максимальна глибина стека для оброблюваного багатокутника.

Тепер залишається лише один цікавий момент. Після заповнення 4-зв'язних полігональних підобластей з затравними пікселями 5, 4 і 3 на рис. 7.34,е зі стека витягується піксель, що позначений цифрою 2. Тут ми виявляємо, що всі пікселі на цьому рядку і на сусідніх рядках (вище і нижче) вже заповнені. Таким чином, ні один піксель в стек не поміщається. Зі стека витягується піксель  1 і рядок обробляється, при цьому знову додаткових пікселів не з'являється. Тепер стек порожній, багатокутник заповнений і робота алгоритму завершена.

Максимальна глибина стека в цьому випадку дорівнює п'яти.

 

 

Приклади реалізації

 

Приклад 7.13. У площині x-y знайти точки перетину площини з колом.

Вершини: P0(4:4), P1(4:26), P2(20:26), P3(28:18), P4(21:4), P5(21:8), P6(10:8), P7(10:4), V0(10:12), V1(10:20), V2(17:20), V3(21:16), V4(21:12)

 

Uses crt,graph;

Type TPoint = record

X,y:integer;

End;

 

Const n=7; m=4; s=10; max_elem=32;

 

Var

grDriver:integer;

grMode:integer;

ErrCode:integer;

P:array[0..n] of tpoint;

V:array[0..m] of tpoint;

R:array[0..max_elem-1,0..20] of integer;

I,j,y,k,bf,im:integer;

Buf:tpoint;

 

Function det(a11,a12,a21,a22:integer):integer;

Begin

Det2:=a11*a22-a12*a21;

End;

 

Procedure myline(p1,p2:tpoint; vara,b,c:integer);

Begin

A:=p2.y-p1.y;

B:=-p2.x+p1.x;

C:=p1.x*(p2.y-p1.y)-p1.y*(p2.x-p1.x);

End;

 

Function mix(a,b:integer):integer;

Begin if a<=b then min:=a else min:=b; end;

Function max(a,b:integer):integer;

Begin if a>=b then max:=a else max:=b; end;

 

Function intersect (p11, p12, p21, p22:tpoint; var p:t point): boolean;

Var d, a1, b1, c1, a2, b2, c2: integer;

Begin

 Line(p11, p12, a1, b1, c1);

 Line(p21, p22, a2, b2, c2);

D:=det2(a1, b1, a2, b2);

If d=0 then peretun:=false

Else begin

p.x:=round(det2(c1,b1,c2,b2)/d);

p.y:=round(det2(a1,c1,a2,c2)/d);

if p.y in [min(p1.y,p2.y)..max(p1.y,p2.y)] then

peretunsc:true;

end;

end;

function intersectline (p1,p2:tpoint; y:integer; var p:tpoint):boolean;

var a,b,c,d:integer;

begin

peretunsc:=false;

priama(p1,p2,a,b,c);

if a<>0 then

begin

p.x:=round((c-b*y)/a);

p.y:=round((a*y)/a);

if p.y in [min(p1.y,p2.y)..max(p1.y,p2.y)] then

peretunsc:=true; end;

end;

begin

grDriver:=detect;

InitGraph(grDriver, grMode,’’);

ErrCode:=GraphResult;

If ErrCode=grOk then

Begin

P[0].x:=4

P[0].y:=4

P[1].x:=4

P[1].y:=26

P[2].x:=20

P[2].y:=26

P[3].x:=28

P[3].y:=18

P[4].x:=21

P[4].y:=4

P[5].x:=21

P[5].y:=18

P[6].x:=10

P[6].y:=8

P[7].x:=10

P[7].y:=4

V[0].x:=10

V[0].y:=12

V[1].x:=10

V[1].y:=20

V[2].x:=17

V[2].y:=20

V[3].x:=21

V[3].y:=16

V[4].x:=21

V[4].y:=12

For i:=0 to max_elem-1 do

For j:=0 to 20 do r [I,j]:=0;

Moveto(p[n].x*s,p[n].y*s);

For i:=0 to n do

Lineto (p[i].x*s,p[i].y*s);

Moveto(v[m].x*s,v[m].y*s);

For i:=0 to m do

Lineto (v[i].x*s,v[i].y*s);

For y:=0 to max_elem-1 do

For i:=1 to n do

If peretunsc (p[i-1],p[i],y,buf) then begin

Circle(buf.x*s, buf.y*s,s div 2);

K:=r[buf.y,0];inc(k);

R[buf.y,k]:=buf.x;

R[buf.y,0]:=k;

End;

For y:=0 to max_elem-1 do

Begin

K:=r[y,0];

For i:=1 to k do begin

im:=i;

For j:=i+1 to k-1 do

If r[y,j]<r[y,im] then im:=j;

Bf:=r[y,im];

R[y,im]:=r[y,i];

R[y,i]:=bf;

End;

End;

For i:=0 to k do writeln(r[i].x:4,r[i].y:4);

For i:=1 to k do

If (r[i].y<r[i-1].y) or ((r[i].y=r[i-1].y) and (r[i].x<r[i=1].x)) then

Begin

Buf:=r[i-1];

R[i-1]:=r[i];

R[i]:=buf;

End;

Readln;

clrscr;

For i:=0 to k do writeln (r[i].4:4,r[i].y:4);

Readln;

Circle(r.x*s,r.y*s,s div 2);

closeGraph;

writeln;

for i:=0 to max_elem-1 do begin

for j:=1 to r[I,0] do

write(r[i,j],’’);

writeln;

end;

end

end.

 

Приклад 7.14. Даний багатокутник, зовнішня частина якого задана точками (4, 5), (4, 26), (20, 26), (28, 18), (21, 4), (21, 8), (10, 8), (10, 4), а внутрішня точками (10, 12), (10, 20), (17, 20), (21, 16), (21, 12) на растрі 32х32. Напишіть програму, в якій реалізовано стрічковий алгоритм з затравкою для заповнення внутрішньої частини багатокутника. Затравка – піксель (14, 20)

 

Uses graph;

Label Label1, Label2, Label3, Label4, Label5, Label6, Label7, Label8;

Type tpoint=record

X,y:integer;

End;

 

Var

grDriver:integer;

grMode:integer;

ErrCode:integer;

Right, left, top, down, x1, x2, y1, y2:integer;

T1, t2: array[1..4]  of integer;

Flag:integer;

P, p1, p2, ppn, pn, pp1, pp2:tpoint;

N, I, h, s1, s2, m, x, y:integer;

 

 

Begin

If x1<left then t1[4]:=1 else t1[4]:=0;

If x1>right then t1[3]:=1 else t1[3]:=0;

If y1<down then t1[2]:=1 else t1[2]:=0;

If x1<top then t1[1]:=1 else t1[1]:=0;

If x2<left then t2[4]:=1 else t2[4]:=0;

If x2<right then t2[3]:=1 else t2[3]:=0;

If y2<down then t2[2]:=1 else t2[2]:=0;

If y2<top then t2[1]:=1 else t2[1]:=0;

Flag:=0;

s1:=0; s2:=0;

Pp1:=p1;

Pp2:=p2;

M:=32000;

For i:=1 to 4 do begin

s1:=s1+t1[i];

s2:=s2+t2[i]; end;

If (s1=0) and (s2=0) then goto Label7;

H:=0;

For i:=1 to 4 do begin

H:=h+trunk((t1[i]+t2[i])/2);

If h<>0 then begin

Flag:=-1;

Goto Label7; end; end;

If s1=0 then begin

N:=1;

Pp1:=p1;

P:=p2;

Goto Label2;

end;

If s2=0 then begin

N:=2;

Pp1:=p2;

P:=p1;

Goto Label2;

end;

N:=0;

Label1: if n<>0 then ppn:=p;

N:=n+1;

If n>2 then goto Label7;

P:=pn;

Label2: if x2-x1=0 then goto Label4;

M:= round((y2-y1)/(x2-x1));

If left < p.x then goto label3;

y:=m*(left-p.x)+p.y;

If y>top then goto Label3;

If y<down then goto Label3;

p.y:=y;

p.x:=Left;

goto Label1;

Label3: if right>p.x then goto Label4;

y:= m(right-p.x)+p.y;

If y>top then goto label4;

If y<down then goto label4;

p.y:=y;

p.x:=right;

goto Label1;

If m=0 then goto Label1;

If top>p.y then goto Label5;

X:=trunk(1/m)*(top-p.y)+p.x;

If x<left then goto Label5;

If x>right then goto Label5;

p.x:=x;

p.y:=top;

goto label1;

If down < p.y then goto Label8;

x:=trunc(1/m*(down-p.y)+p.x);

If x<left then goto Label6;

If x>right then goto Label6;

p.x:=x;

p.y:=down;

goto label1;

Label6: flag:=-1;

Label7: if flag=-1 then goto Label 8;

grDriver:=detect;

initGraph(grDriver, grMode,’’);

ErrCode:=Graphresult;

If ErrCode:=grOk then begin

Line(pp1.x,pp1.y,pp2.x,pp2.y);

End;

Readln;

Label8;

End.