GR32_VPR.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. unit GR32_VPR;
  2. (* ***** BEGIN LICENSE BLOCK *****
  3. * Version: MPL 1.1 or LGPL 2.1 with linking exception
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. * http://www.mozilla.org/MPL/
  9. *
  10. * Software distributed under the License is distributed on an "AS IS" basis,
  11. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. * for the specific language governing rights and limitations under the
  13. * License.
  14. *
  15. * Alternatively, the contents of this file may be used under the terms of the
  16. * Free Pascal modified version of the GNU Lesser General Public License
  17. * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
  18. * of this license are applicable instead of those above.
  19. * Please see the file LICENSE.txt for additional information concerning this
  20. * license.
  21. *
  22. * The Original Code is Vectorial Polygon Rasterizer for Graphics32
  23. *
  24. * The Initial Developer of the Original Code is
  25. * Mattias Andersson <[email protected]>
  26. *
  27. * Portions created by the Initial Developer are Copyright (C) 2008-2012
  28. * the Initial Developer. All Rights Reserved.
  29. *
  30. * Contributor(s):
  31. *
  32. * ***** END LICENSE BLOCK ***** *)
  33. interface
  34. {$I GR32.inc}
  35. uses
  36. GR32;
  37. type
  38. PSingleArray = GR32.PSingleArray;
  39. TValueSpan = record
  40. X1, X2: Integer;
  41. Values: PSingleArray;
  42. end;
  43. TRenderSpanEvent = procedure(const Span: TValueSpan; DstY: Integer) of object;
  44. TRenderSpanProc = procedure(Data: Pointer; const Span: TValueSpan; DstY: Integer);
  45. procedure RenderPolyPolygon(const Points: TArrayOfArrayOfFloatPoint;
  46. const ClipRect: TFloatRect; const RenderProc: TRenderSpanProc; Data: Pointer = nil); overload;
  47. procedure RenderPolygon(const Points: TArrayOfFloatPoint;
  48. const ClipRect: TFloatRect; const RenderProc: TRenderSpanProc; Data: Pointer = nil); overload;
  49. procedure RenderPolyPolygon(const Points: TArrayOfArrayOfFloatPoint;
  50. const ClipRect: TFloatRect; const RenderProc: TRenderSpanEvent); overload;
  51. procedure RenderPolygon(const Points: TArrayOfFloatPoint;
  52. const ClipRect: TFloatRect; const RenderProc: TRenderSpanEvent); overload;
  53. implementation
  54. {$if Defined(COMPILERFPC) and Defined(CPUx86_64) }
  55. // Must apply work around for negative array index on FPC 64-bit.
  56. // See:
  57. // - https://github.com/graphics32/graphics32/issues/51
  58. // - https://forum.lazarus.freepascal.org/index.php/topic,44655.0.html
  59. {$define NEGATIVE_INDEX_64}
  60. {$ifend}
  61. uses
  62. Math, GR32_Math, GR32_LowLevel, GR32_VectorUtils;
  63. type
  64. PLineSegment = ^TLineSegment;
  65. TLineSegment = array [0..1] of TFloatPoint;
  66. PLineSegmentArray = ^TLineSegmentArray;
  67. TLineSegmentArray = array [0..0] of TLineSegment;
  68. PScanLine = ^TScanLine;
  69. TScanLine = record
  70. Segments: PLineSegmentArray;
  71. Count: Integer;
  72. Y: Integer;
  73. end;
  74. TScanLines = array of TScanLine;
  75. PScanLineArray = ^TScanLineArray;
  76. TScanLineArray = array [0..0] of TScanLine;
  77. procedure IntegrateSegment(var P1, P2: TFloatPoint; Values: PSingleArray);
  78. var
  79. {$if Defined(NEGATIVE_INDEX_64) }
  80. X1, X2: Int64;
  81. {$else}
  82. X1, X2: Integer;
  83. {$ifend}
  84. I: Integer;
  85. Dx, Dy, DyDx, Sx, Y, fracX1, fracX2: TFloat;
  86. begin
  87. X1 := Round(P1.X);
  88. X2 := Round(P2.X);
  89. if X1 = X2 then
  90. begin
  91. Values[X1] := Values[X1] + 0.5 * (P2.X - P1.X) * (P1.Y + P2.Y);
  92. end
  93. else
  94. begin
  95. fracX1 := P1.X - X1;
  96. fracX2 := P2.X - X2;
  97. Dx := P2.X - P1.X;
  98. Dy := P2.Y - P1.Y;
  99. DyDx := Dy/Dx;
  100. if X1 < X2 then
  101. begin
  102. Sx := 1 - fracX1;
  103. Y := P1.Y + Sx * DyDx;
  104. Values[X1] := Values[X1] + 0.5 * (P1.Y + Y) * Sx;
  105. for I := X1 + 1 to X2 - 1 do
  106. begin
  107. Values[I] := Values[I] + (Y + DyDx * 0.5); // N: Sx = 1
  108. Y := Y + DyDx;
  109. end;
  110. Sx := fracX2;
  111. Values[X2] := Values[X2] + 0.5 * (Y + P2.Y) * Sx;
  112. end
  113. else // X1 > X2
  114. begin
  115. Sx := fracX1;
  116. Y := P1.Y - Sx * DyDx;
  117. Values[X1] := Values[X1] - 0.5 * (P1.Y + Y) * Sx;
  118. for I := X1 - 1 downto X2 + 1 do
  119. begin
  120. Values[I] := Values[I] - (Y - DyDx * 0.5); // N: Sx = -1
  121. Y := Y - DyDx;
  122. end;
  123. Sx := 1 - fracX2;
  124. Values[X2] := Values[X2] - 0.5 * (Y + P2.Y) * Sx;
  125. end;
  126. end;
  127. end;
  128. procedure ExtractSingleSpan(const ScanLine: TScanLine; out Span: TValueSpan;
  129. SpanData: PSingleArray);
  130. var
  131. I: Integer;
  132. {$if Defined(NEGATIVE_INDEX_64) }
  133. X: Int64;
  134. {$else}
  135. X: Integer;
  136. {$ifend}
  137. P: PFloatPoint;
  138. S: PLineSegment;
  139. fracX: TFloat;
  140. Points: PFloatPointArray;
  141. N: Integer;
  142. begin
  143. N := ScanLine.Count * 2;
  144. Points := @ScanLine.Segments[0];
  145. Span.X1 := High(Integer);
  146. Span.X2 := Low(Integer);
  147. P := @Points[0];
  148. for I := 0 to N - 1 do
  149. begin
  150. X := Round(P.X);
  151. if X < Span.X1 then Span.X1 := X;
  152. if P.Y = 1 then
  153. begin
  154. fracX := P.X - X;
  155. if Odd(I) then
  156. begin
  157. SpanData[X] := SpanData[X] + (1 - fracX); Inc(X);
  158. SpanData[X] := SpanData[X] + fracX;
  159. end
  160. else
  161. begin
  162. SpanData[X] := SpanData[X] - (1 - fracX); Inc(X);
  163. SpanData[X] := SpanData[X] - fracX;
  164. end;
  165. end;
  166. if X > Span.X2 then Span.X2 := X;
  167. inc(P);
  168. end;
  169. X := Span.X1; // Use X so NEGATIVE_INDEX_64 is handled
  170. Span.Values := @SpanData[X];
  171. CumSum(Span.Values, Span.X2 - Span.X1 + 1);
  172. for I := 0 to ScanLine.Count - 1 do
  173. begin
  174. S := @ScanLine.Segments[I];
  175. IntegrateSegment(S[0], S[1], SpanData);
  176. end;
  177. end;
  178. procedure AddSegment(const X1, Y1, X2, Y2: TFloat; var ScanLine: TScanLine); {$IFDEF USEINLINING} inline; {$ENDIF}
  179. var
  180. S: PLineSegment;
  181. begin
  182. if (Y1 = 0) and (Y2 = 0) then Exit; {** needed for proper clipping }
  183. with ScanLine do
  184. begin
  185. S := @Segments[Count];
  186. Inc(Count);
  187. end;
  188. S[0].X := X1;
  189. S[0].Y := Y1;
  190. S[1].X := X2;
  191. S[1].Y := Y2;
  192. end;
  193. procedure DivideSegment(var P1, P2: TFloatPoint; const ScanLines: PScanLineArray);
  194. var
  195. Y, Y1, Y2: Integer;
  196. k, X, X2: TFloat;
  197. begin
  198. Y1 := Round(P1.Y);
  199. Y2 := Round(P2.Y);
  200. if Y1 = Y2 then
  201. begin
  202. AddSegment(P1.X, P1.Y - Y1, P2.X, P2.Y - Y1, ScanLines[Y1]);
  203. end
  204. else
  205. begin
  206. k := (P2.X - P1.X) / (P2.Y - P1.Y);
  207. // k is expanded below to limit rounding errors.
  208. if Y1 < Y2 then
  209. begin
  210. X := P1.X + (Y1 + 1 - P1.Y) * { k } (P2.X - P1.X) / (P2.Y - P1.Y);
  211. AddSegment(P1.X, P1.Y - Y1, X, 1, ScanLines[Y1]);
  212. for Y := Y1 + 1 to Y2 - 1 do
  213. begin
  214. X2 := X + k;
  215. AddSegment(X, 0, X2, 1, ScanLines[Y]);
  216. X := X2;
  217. end;
  218. AddSegment(X, 0, P2.X, P2.Y - Y2, ScanLines[Y2]);
  219. end
  220. else
  221. begin
  222. X := P1.X + (Y1 - P1.Y) * { k } (P2.X - P1.X) / (P2.Y - P1.Y);
  223. AddSegment(P1.X, P1.Y - Y1, X, 0, ScanLines[Y1]);
  224. for Y := Y1 - 1 downto Y2 + 1 do
  225. begin
  226. X2 := X - k;
  227. AddSegment(X, 1, X2, 0, ScanLines[Y]);
  228. X := X2;
  229. end;
  230. AddSegment(X, 1, P2.X, P2.Y - Y2, ScanLines[Y2]);
  231. end;
  232. end;
  233. end;
  234. procedure BuildScanLines(const Points: TArrayOfArrayOfFloatPoint;
  235. out ScanLines: TScanLines);
  236. var
  237. I,J,K, M,N, Y0,Y1,Y, YMin,YMax: Integer;
  238. PY: PSingle;
  239. PPt1, PPt2: PFloatPoint;
  240. PScanLines: PScanLineArray;
  241. begin
  242. YMin := MaxInt;
  243. YMax := -MaxInt;
  244. M := High(Points);
  245. for K := 0 to M do
  246. begin
  247. N := High(Points[K]);
  248. if N < 2 then Continue;
  249. PY := @Points[K][0].Y;
  250. for I := 0 to N do
  251. begin
  252. Y := Round(PY^);
  253. if YMin > Y then YMin := Y;
  254. if YMax < Y then YMax := Y;
  255. inc(PY, 2); // skips X value
  256. end;
  257. end;
  258. if YMin > YMax then Exit;
  259. SetLength(ScanLines, YMax - YMin + 2);
  260. PScanLines := @ScanLines[-YMin];
  261. {** compute array sizes for each scanline }
  262. for K := 0 to M do
  263. begin
  264. N := High(Points[K]);
  265. if N < 2 then Continue;
  266. Y0 := Round(Points[K][N].Y);
  267. PY := @Points[K][0].Y;
  268. for I := 0 to N do
  269. begin
  270. Y1 := Round(PY^);
  271. if Y0 <= Y1 then
  272. begin
  273. Inc(PScanLines[Y0].Count);
  274. Dec(PScanLines[Y1 + 1].Count);
  275. end
  276. else
  277. begin
  278. Inc(PScanLines[Y1].Count);
  279. Dec(PScanLines[Y0 + 1].Count);
  280. end;
  281. Y0 := Y1;
  282. inc(PY, 2); // skips X value
  283. end;
  284. end;
  285. {** allocate memory }
  286. J := 0;
  287. for I := 0 to High(ScanLines) do
  288. begin
  289. Inc(J, ScanLines[I].Count);
  290. GetMem(ScanLines[I].Segments, J * SizeOf(TLineSegment));
  291. ScanLines[I].Count := 0;
  292. ScanLines[I].Y := YMin + I;
  293. end;
  294. for K := 0 to M do
  295. begin
  296. N := High(Points[K]);
  297. if N < 2 then Continue;
  298. PPt1 := @Points[K][N];
  299. PPt2 := @Points[K][0];
  300. for I := 0 to N do
  301. begin
  302. DivideSegment(PPt1^, PPt2^, PScanLines);
  303. PPt1 := PPt2;
  304. Inc(PPt2);
  305. end;
  306. end;
  307. end;
  308. procedure RenderScanline(var ScanLine: TScanLine;
  309. RenderProc: TRenderSpanProc; Data: Pointer; SpanData: PSingleArray; X1, X2: Integer);
  310. var
  311. Span: TValueSpan;
  312. {$if Defined(NEGATIVE_INDEX_64) }
  313. X: Int64;
  314. {$else}
  315. X: Integer;
  316. {$ifend}
  317. begin
  318. if ScanLine.Count > 0 then
  319. begin
  320. ExtractSingleSpan(ScanLine, Span, SpanData);
  321. if Span.X1 < X1 then Span.X1 := X1;
  322. if Span.X2 > X2 then Span.X2 := X2;
  323. if Span.X2 < Span.X1 then Exit;
  324. RenderProc(Data, Span, ScanLine.Y);
  325. X := Span.X1;
  326. FillLongWord(SpanData[X], Span.X2 - Span.X1 + 1, 0);
  327. end;
  328. end;
  329. {$ifndef COMPILERXE2_UP}
  330. type
  331. TRoundingMode = Math.TFPURoundingMode;
  332. {$endif COMPILERXE2_UP}
  333. procedure RenderPolyPolygon(const Points: TArrayOfArrayOfFloatPoint;
  334. const ClipRect: TFloatRect; const RenderProc: TRenderSpanProc; Data: Pointer);
  335. var
  336. ScanLines: TScanLines;
  337. I, Len: Integer;
  338. Poly: TArrayOfArrayOfFloatPoint;
  339. SavedRoundMode: TRoundingMode;
  340. CX1, CX2: Integer;
  341. SpanData: PSingleArray;
  342. begin
  343. Len := Length(Points);
  344. if Len = 0 then
  345. Exit;
  346. SavedRoundMode := SetRoundMode(rmDown);
  347. try
  348. SetLength(Poly, Len);
  349. for i := 0 to Len -1 do
  350. Poly[i] := ClipPolygon(Points[i], ClipRect);
  351. BuildScanLines(Poly, ScanLines);
  352. CX1 := Round(ClipRect.Left);
  353. CX2 := -Round(-ClipRect.Right) - 1;
  354. I := CX2 - CX1 + 4;
  355. GetMem(SpanData, I * SizeOf(Single));
  356. FillLongWord(SpanData^, I, 0);
  357. for I := 0 to High(ScanLines) do
  358. begin
  359. RenderScanline(ScanLines[I], RenderProc, Data, @SpanData[-CX1 + 1], CX1, CX2);
  360. FreeMem(ScanLines[I].Segments);
  361. end;
  362. FreeMem(SpanData);
  363. finally
  364. SetRoundMode(SavedRoundMode);
  365. end;
  366. end;
  367. procedure RenderPolygon(const Points: TArrayOfFloatPoint;
  368. const ClipRect: TFloatRect; const RenderProc: TRenderSpanProc; Data: Pointer);
  369. begin
  370. RenderPolyPolygon(PolyPolygon(Points), ClipRect, RenderProc, Data);
  371. end;
  372. procedure RenderPolyPolygon(const Points: TArrayOfArrayOfFloatPoint;
  373. const ClipRect: TFloatRect; const RenderProc: TRenderSpanEvent);
  374. begin
  375. with TMethod(RenderProc) do
  376. RenderPolyPolygon(Points, ClipRect, TRenderSpanProc(Code), Data);
  377. end;
  378. procedure RenderPolygon(const Points: TArrayOfFloatPoint;
  379. const ClipRect: TFloatRect; const RenderProc: TRenderSpanEvent);
  380. begin
  381. with TMethod(RenderProc) do
  382. RenderPolygon(Points, ClipRect, TRenderSpanProc(Code), Data);
  383. end;
  384. end.