fills.inc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 1999-2000 by Thomas Schatzl and Carl Eric Codere
  4. This include implements polygon filling and flood filling.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. { simple descriptive name }
  12. function max(a, b : Longint) : Longint;
  13. begin
  14. max := b;
  15. if (a > b) then max := a;
  16. end;
  17. { here too }
  18. function min(a, b : Longint) : Longint;
  19. begin
  20. min := b;
  21. if (a < b) then min := a;
  22. end;
  23. procedure fillpoly(numpoints : Word; var polypoints);
  24. { disable range check mode }
  25. {$push}
  26. {$R-}
  27. type
  28. pedge = ^tedge;
  29. tedge = packed record
  30. yMin, yMax, x, dX, dY, frac : Longint;
  31. end;
  32. pedgearray = ^tedgearray;
  33. tedgearray = array[0..0] of tedge;
  34. ppedgearray = ^tpedgearray;
  35. tpedgearray = array[0..0] of pedge;
  36. var
  37. nActive, nNextEdge : Longint;
  38. p0, p1 : pointtype;
  39. i, j, gap, x0, x1, y, nEdges : Longint;
  40. ET : pedgearray;
  41. GET, AET : ppedgearray;
  42. t : pedge;
  43. ptable : ^pointtype;
  44. LastPolygonStart : Longint;
  45. Closing, PrevClosing : Boolean;
  46. begin
  47. { /********************************************************************
  48. * Add entries to the global edge table. The global edge table has a
  49. * bucket for each scan line in the polygon. Each bucket contains all
  50. * the edges whose yMin == yScanline. Each bucket contains the yMax,
  51. * the x coordinate at yMax, and the denominator of the slope (dX)
  52. */}
  53. getmem(et, sizeof(tedge) * numpoints);
  54. getmem(get, sizeof(pedge) * numpoints);
  55. getmem(aet, sizeof(pedge) * numpoints);
  56. ptable := @polypoints;
  57. { check for getmem success }
  58. nEdges := 0;
  59. LastPolygonStart := 0;
  60. Closing := false;
  61. for i := 0 to (numpoints-1) do begin
  62. p0 := ptable[i];
  63. if (i+1) >= numpoints then p1 := ptable[0]
  64. else p1 := ptable[i+1];
  65. { save the 'closing' flag for the previous edge }
  66. PrevClosing := Closing;
  67. { check if the current edge is 'closing'. This means that it 'closes'
  68. the polygon by going back to the first point of the polygon.
  69. Also, 0-length edges are never considered 'closing'. }
  70. if ((p1.x <> ptable[i].x) or
  71. (p1.y <> ptable[i].y)) and
  72. (LastPolygonStart < i) and
  73. ((p1.x = ptable[LastPolygonStart].x) and
  74. (p1.y = ptable[LastPolygonStart].y)) then
  75. begin
  76. Closing := true;
  77. LastPolygonStart := i + 2;
  78. end
  79. else
  80. Closing := false;
  81. { skip current edge if the previous edge was 'closing'. This is TP7 compatible }
  82. if PrevClosing then
  83. continue;
  84. { draw the edges }
  85. { nickysn: moved after drawing the filled area
  86. Line(p0.x,p0.y,p1.x,p1.y);}
  87. { ignore if this is a horizontal edge}
  88. if (p0.y = p1.y) then continue;
  89. { swap ptable if necessary to ensure p0 contains yMin}
  90. if (p0.y > p1.y) then begin
  91. p0 := p1;
  92. p1 := ptable[i];
  93. end;
  94. { create the new edge }
  95. et^[nEdges].ymin := p0.y;
  96. et^[nEdges].ymax := p1.y;
  97. et^[nEdges].x := p0.x;
  98. et^[nEdges].dX := p1.x-p0.x;
  99. et^[nEdges].dy := p1.y-p0.y;
  100. et^[nEdges].frac := 0;
  101. get^[nEdges] := @et^[nEdges];
  102. inc(nEdges);
  103. end;
  104. { sort the GET on ymin }
  105. gap := 1;
  106. while (gap < nEdges) do gap := 3*gap+1;
  107. gap := gap div 3;
  108. while (gap > 0) do begin
  109. for i := gap to (nEdges-1) do begin
  110. j := i - gap;
  111. while (j >= 0) do begin
  112. if (GET^[j]^.ymin <= GET^[j+gap]^.yMin) then break;
  113. t := GET^[j];
  114. GET^[j] := GET^[j+gap];
  115. GET^[j+gap] := t;
  116. dec(j, gap);
  117. end;
  118. end;
  119. gap := gap div 3;
  120. end;
  121. { initialize the active edge table, and set y to first entering edge}
  122. nActive := 0;
  123. nNextEdge := 0;
  124. y := GET^[nNextEdge]^.ymin;
  125. { Now process the edges using the scan line algorithm. Active edges
  126. will be added to the Active Edge Table (AET), and inactive edges will
  127. be deleted. X coordinates will be updated with incremental integer
  128. arithmetic using the slope (dY / dX) of the edges. }
  129. while (nNextEdge < nEdges) or (nActive <> 0) do begin
  130. {Move from the ET bucket y to the AET those edges whose yMin == y
  131. (entering edges) }
  132. while (nNextEdge < nEdges) and (GET^[nNextEdge]^.ymin = y) do begin
  133. AET^[nActive] := GET^[nNextEdge];
  134. inc(nActive);
  135. inc(nNextEdge);
  136. end;
  137. { Remove from the AET those entries for which yMax == y (leaving
  138. edges) }
  139. i := 0;
  140. while (i < nActive) do begin
  141. if (AET^[i]^.yMax = y) then begin
  142. dec(nActive);
  143. System.move(AET^[i+1], AET^[i], (nActive-i)*sizeof(pedge));
  144. end else
  145. inc(i);
  146. end;
  147. if (y >= 0) then begin
  148. {Now sort the AET on x. Since the list is usually quite small,
  149. the sort is implemented as a simple non-recursive shell sort }
  150. gap := 1;
  151. while (gap < nActive) do gap := 3*gap+1;
  152. gap := gap div 3;
  153. while (gap > 0) do begin
  154. for i := gap to (nActive-1) do begin
  155. j := i - gap;
  156. while (j >= 0) do begin
  157. if (AET^[j]^.x <= AET^[j+gap]^.x) then break;
  158. t := AET^[j];
  159. AET^[j] := AET^[j+gap];
  160. AET^[j+gap] := t;
  161. dec(j, gap);
  162. end;
  163. end;
  164. gap := gap div 3;
  165. end;
  166. { Fill in desired pixels values on scan line y by using pairs of x
  167. coordinates from the AET }
  168. i := 0;
  169. while (i < (nActive - 1)) do begin
  170. x0 := AET^[i]^.x;
  171. x1 := AET^[i+1]^.x;
  172. {Left edge adjustment for positive fraction. 0 is interior. }
  173. if (AET^[i]^.frac >= 0) then inc(x0);
  174. {Right edge adjustment for negative fraction. 0 is exterior. }
  175. if (AET^[i+1]^.frac <= 0) then dec(x1);
  176. x0 := max(x0, 0);
  177. x1 := min(x1, viewWidth);
  178. { Draw interior spans}
  179. if (x1 >= x0) then begin
  180. PatternLine(x0, x1, y);
  181. end;
  182. inc(i, 2);
  183. end;
  184. end;
  185. { Update all the x coordinates. Edges are scan converted using a
  186. modified midpoint algorithm (Bresenham's algorithm reduces to the
  187. midpoint algorithm for two dimensional lines) }
  188. for i := 0 to (nActive-1) do begin
  189. t := AET^[i];
  190. { update the fraction by dX}
  191. inc(t^.frac, t^.dX);
  192. if (t^.dX < 0) then
  193. while ( -(t^.frac) >= t^.dY) do begin
  194. inc(t^.frac, t^.dY);
  195. dec(t^.x);
  196. end
  197. else
  198. while (t^.frac >= t^.dY) do begin
  199. dec(t^.frac, t^.dY);
  200. inc(t^.x);
  201. end;
  202. end;
  203. inc(y);
  204. if (y >= ViewHeight) then break;
  205. end;
  206. { finally, draw the edges }
  207. LastPolygonStart := 0;
  208. Closing := false;
  209. for i := 0 to (numpoints-1) do begin
  210. p0 := ptable[i];
  211. if (i+1) >= numpoints then p1 := ptable[0]
  212. else p1 := ptable[i+1];
  213. { save the 'closing' flag for the previous edge }
  214. PrevClosing := Closing;
  215. { check if the current edge is 'closing'. This means that it 'closes'
  216. the polygon by going back to the first point of the polygon.
  217. Also, 0-length edges are never considered 'closing'. }
  218. if ((p1.x <> p0.x) or
  219. (p1.y <> p0.y)) and
  220. (LastPolygonStart < i) and
  221. ((p1.x = ptable[LastPolygonStart].x) and
  222. (p1.y = ptable[LastPolygonStart].y)) then
  223. begin
  224. Closing := true;
  225. LastPolygonStart := i + 2;
  226. end
  227. else
  228. Closing := false;
  229. { skip edge if the previous edge was 'closing'. This is TP7 compatible }
  230. if PrevClosing then
  231. continue;
  232. { draw the edges }
  233. Line(p0.x,p0.y,p1.x,p1.y);
  234. end;
  235. System.freemem(et, sizeof(tedge) * numpoints);
  236. System.freemem(get, sizeof(pedge) * numpoints);
  237. System.freemem(aet, sizeof(pedge) * numpoints);
  238. end;
  239. { maximum supported Y resultion }
  240. const
  241. MaxYRes = 2048;
  242. { changing this to 1 or 2 doesn't improve performance noticably }
  243. YResDiv = 4;
  244. type
  245. PFloodLine = ^TFloodLine;
  246. TFloodLine = record
  247. next: PFloodLine;
  248. x1 : smallint;
  249. x2 : smallint;
  250. y : smallint;
  251. end;
  252. PDrawnList = ^TDrawnList;
  253. TDrawnList = Array[0..(MaxYRes - 1) div YResDiv] of PFloodLine;
  254. var
  255. DrawnList : PDrawnList;
  256. Buffer : Record { Union for byte and word addressing of buffer }
  257. ByteIndex : Word;
  258. WordIndex : Word;
  259. Case Boolean Of
  260. False : (Bytes : Array [0..StdBufferSize-1] Of Byte);
  261. True : (Words : Array [0..(StdBufferSize DIV 2)-1] Of Word);
  262. End;
  263. s1, s2, s3 : PWordArray; { Three buffers for scanlines }
  264. {$ifdef FPC_GRAPH_SUPPORTS_TRUECOLOR}
  265. sl1 : PLongWordArray absolute s1;
  266. sl2 : PLongWordArray absolute s2;
  267. sl3 : PLongWordArray absolute s3;
  268. {$endif FPC_GRAPH_SUPPORTS_TRUECOLOR}
  269. Procedure PushPoint (x, y : smallint);
  270. {********************************************************}
  271. { Adds a point to the list of points to check if we }
  272. { need to draw. Doesn't add the point if there is a }
  273. { buffer overflow. }
  274. {********************************************************}
  275. Begin
  276. If Buffer.WordIndex<(StdBufferSize DIV 2)-3 then
  277. Begin
  278. Buffer.Words[Buffer.WordIndex]:=x;
  279. Buffer.Words[Buffer.WordIndex+1]:=y;
  280. Inc (Buffer.WordIndex,2);
  281. End
  282. End;
  283. Procedure PopPoint (Var x, y : smallint);
  284. {********************************************************}
  285. { Removes a point from the list of points to check, if }
  286. { we try to access an illegal point, then the routine }
  287. { returns -1,-1 as a coordinate pair. }
  288. {********************************************************}
  289. Begin
  290. If Buffer.WordIndex>1 then
  291. Begin
  292. x:=Buffer.Words[Buffer.WordIndex-2];
  293. y:=Buffer.Words[Buffer.WordIndex-1];
  294. Dec (Buffer.WordIndex,2);
  295. End
  296. Else
  297. Begin
  298. x:=-1;
  299. y:=-1;
  300. End;
  301. End;
  302. {********************************************************}
  303. { Procedure AddLinePoints() }
  304. {--------------------------------------------------------}
  305. { Adds a line segment to the list of lines which will be }
  306. { drawn to the screen. The line added is on the specified}
  307. { Y axis, from the x1 to x2 coordinates. }
  308. {********************************************************}
  309. Procedure AddLinePoints(x1,x2,y: smallint);
  310. var temp: PFloodLine;
  311. begin
  312. new(temp);
  313. temp^.x1 := x1;
  314. temp^.x2 := x2;
  315. temp^.y := y;
  316. temp^.next := DrawnList^[y div YResDiv];
  317. DrawnList^[y div YResDiv] := temp;
  318. end;
  319. {********************************************************}
  320. { Procedure AlreadyDrawn() }
  321. {--------------------------------------------------------}
  322. { This routine searches through the list of segments }
  323. { which will be drawn to the screen, and determines if }
  324. { the specified point (x,y) will already be drawn. }
  325. { i.e : Checks if the x,y point lies within a known }
  326. { segment which will be drawn to the screen. This makes }
  327. { sure that we don't draw some segments two times. }
  328. { Return TRUE if the point is already in the segment list}
  329. { to draw, otherwise returns FALSE. }
  330. {********************************************************}
  331. Function AlreadyDrawn(x, y: smallint): boolean;
  332. var
  333. temp : PFloodLine;
  334. begin
  335. AlreadyDrawn := false;
  336. temp := DrawnList^[y div YResDiv];
  337. while assigned(temp) do
  338. begin
  339. if (temp^.y = y) and
  340. (temp^.x1 <= x) and
  341. (temp^.x2 >= x) then
  342. begin
  343. AlreadyDrawn := true;
  344. exit;
  345. end;
  346. temp := temp^.next;
  347. end;
  348. end;
  349. {********************************************************}
  350. { Procedure CleanUpDrawnList }
  351. {--------------------------------------------------------}
  352. { removes all elements from the DrawnList. Doesn't init }
  353. { elements of it with NILL }
  354. {********************************************************}
  355. Procedure CleanUpDrawnList;
  356. var
  357. l: smallint;
  358. temp1, temp2: PFloodLine;
  359. begin
  360. for l := 0 to ViewHeight div YResDiv do
  361. begin
  362. temp1 := DrawnList^[l];
  363. while assigned(temp1) do
  364. begin
  365. temp2 := temp1;
  366. temp1 := temp1^.next;
  367. dispose(temp2);
  368. end;
  369. end;
  370. end;
  371. Procedure FloodFill (x, y : smallint; Border: ColorType);
  372. {********************************************************}
  373. { Procedure FloodFill() }
  374. {--------------------------------------------------------}
  375. { This routine fills a region of the screen bounded by }
  376. { the <Border> color. It uses the current fillsettings }
  377. { for the flood filling. Clipping is supported, and }
  378. { coordinates are local/viewport relative. }
  379. {********************************************************}
  380. Var
  381. stemp: PWordArray;
  382. Beginx : smallint;
  383. d, e : Byte;
  384. Cont : Boolean;
  385. BackupColor : ColorType;
  386. x1, x2, prevy: smallint;
  387. Begin
  388. GetMem(DrawnList,sizeof(PFloodLine)*((ViewHeight div YResDiv) + 1));
  389. FillChar(DrawnList^,sizeof(PFloodLine)*((ViewHeight div YResDiv) + 1),0);
  390. { init prevy }
  391. prevy := 32767;
  392. { Save current drawing color }
  393. BackupColor := CurrentColor;
  394. CurrentColor := FillSettings.Color;
  395. { MaxX is based on zero index }
  396. {$ifdef FPC_GRAPH_SUPPORTS_TRUECOLOR}
  397. if MaxColor > 65536 then
  398. begin
  399. GetMem (s1,(ViewWidth+1)*4); { A pixel color represents a word }
  400. GetMem (s2,(ViewWidth+1)*4); { A pixel color represents a word }
  401. GetMem (s3,(ViewWidth+1)*4); { A pixel color represents a word }
  402. end
  403. else
  404. {$endif FPC_GRAPH_SUPPORTS_TRUECOLOR}
  405. begin
  406. GetMem (s1,(ViewWidth+1)*2); { A pixel color represents a word }
  407. GetMem (s2,(ViewWidth+1)*2); { A pixel color represents a word }
  408. GetMem (s3,(ViewWidth+1)*2); { A pixel color represents a word }
  409. end;
  410. if (not assigned(s1)) or (not assigned(s2)) or (not assigned(s3)) then
  411. begin
  412. _GraphResult := grNoFloodMem;
  413. exit;
  414. end;
  415. If (x<0) Or (y<0) Or
  416. (x>ViewWidth) Or (y>ViewHeight) then Exit;
  417. { Index of points to check }
  418. Buffer.WordIndex:=0;
  419. PushPoint (x,y);
  420. While Buffer.WordIndex>0 Do
  421. Begin
  422. PopPoint (x,y);
  423. { Get the complete lines for the following }
  424. If y <> prevy then
  425. begin
  426. If (prevy - y = 1) then
  427. { previous line was one below the new one, so the previous s2 }
  428. { = new s1 }
  429. Begin
  430. stemp := s3;
  431. s3 := s1;
  432. s1 := s2;
  433. s2 := stemp;
  434. GetScanline(0,ViewWidth,y-1,s2^);
  435. End
  436. Else If (y - prevy = 1) then
  437. { previous line was one above the new one, so the previous s3 }
  438. { = new s1 }
  439. Begin
  440. stemp := s2;
  441. s2 := s1;
  442. s1 := s3;
  443. s3 := stemp;
  444. GetScanline(0,ViewWidth,y+1,s3^);
  445. End
  446. Else
  447. begin
  448. GetScanline(0,ViewWidth,y-1,s2^);
  449. GetScanline(0,ViewWidth,y,s1^);
  450. GetScanline(0,ViewWidth,y+1,s3^);
  451. end;
  452. end;
  453. prevy := y;
  454. { check the current scan line }
  455. {$ifdef FPC_GRAPH_SUPPORTS_TRUECOLOR}
  456. if MaxColor > 65536 then
  457. begin
  458. While (sl1^[x]<>Border) And (x<=ViewWidth) Do Inc (x);
  459. end
  460. else
  461. {$endif FPC_GRAPH_SUPPORTS_TRUECOLOR}
  462. begin
  463. While (s1^[x]<>Border) And (x<=ViewWidth) Do Inc (x);
  464. end;
  465. d:=0;
  466. e:=0;
  467. dec(x);
  468. Beginx:=x;
  469. {$ifdef FPC_GRAPH_SUPPORTS_TRUECOLOR}
  470. if MaxColor > 65536 then
  471. begin
  472. REPEAT
  473. { check the above line }
  474. If y<ViewHeight then
  475. Begin
  476. Cont:=(sl3^[x]<>Border) and (not AlreadyDrawn(x,y+1));
  477. If (e=0) And Cont then
  478. Begin
  479. PushPoint (x,y+1);
  480. e:=1;
  481. End
  482. Else
  483. If (e=1) And Not Cont then e:=0;
  484. End;
  485. { check the line below }
  486. If (y>0) then
  487. Begin
  488. Cont:=(sl2^[x]<>Border) and (not AlreadyDrawn(x,y-1));
  489. If (d=0) And Cont then
  490. Begin
  491. PushPoint (x,y-1);
  492. d:=1;
  493. End
  494. Else
  495. If (d=1) And Not Cont then d:=0;
  496. End;
  497. Dec (x);
  498. Until (x<0) Or (sl1^[x]=Border);
  499. end
  500. else
  501. {$endif FPC_GRAPH_SUPPORTS_TRUECOLOR}
  502. begin
  503. REPEAT
  504. { check the above line }
  505. If y<ViewHeight then
  506. Begin
  507. Cont:=(s3^[x]<>Border) and (not AlreadyDrawn(x,y+1));
  508. If (e=0) And Cont then
  509. Begin
  510. PushPoint (x,y+1);
  511. e:=1;
  512. End
  513. Else
  514. If (e=1) And Not Cont then e:=0;
  515. End;
  516. { check the line below }
  517. If (y>0) then
  518. Begin
  519. Cont:=(s2^[x]<>Border) and (not AlreadyDrawn(x,y-1));
  520. If (d=0) And Cont then
  521. Begin
  522. PushPoint (x,y-1);
  523. d:=1;
  524. End
  525. Else
  526. If (d=1) And Not Cont then d:=0;
  527. End;
  528. Dec (x);
  529. Until (x<0) Or (s1^[x]=Border);
  530. end;
  531. { swap the values }
  532. x1:=x+1;
  533. x2:=BeginX;
  534. if x1 > x2 then
  535. Begin
  536. x:=x1;
  537. x1:=x2;
  538. x2:=x;
  539. end;
  540. { Add to the list of drawn lines }
  541. AddLinePoints(x1,x2,y);
  542. PatternLine (x1,x2,y);
  543. End; { end while }
  544. {$ifdef FPC_GRAPH_SUPPORTS_TRUECOLOR}
  545. if MaxColor > 65536 then
  546. begin
  547. System.FreeMem (s1,(ViewWidth+1)*4);
  548. System.FreeMem (s2,(ViewWidth+1)*4);
  549. System.FreeMem (s3,(ViewWidth+1)*4);
  550. end
  551. else
  552. {$endif FPC_GRAPH_SUPPORTS_TRUECOLOR}
  553. begin
  554. System.FreeMem (s1,(ViewWidth+1)*2);
  555. System.FreeMem (s2,(ViewWidth+1)*2);
  556. System.FreeMem (s3,(ViewWidth+1)*2);
  557. end;
  558. CleanUpDrawnList;
  559. System.FreeMem(DrawnList,sizeof(PFloodLine)*((ViewHeight div YResDiv) + 1));
  560. CurrentColor := BackUpColor;
  561. End;
  562. { restore previous range check mode }
  563. {$pop}