UnitMain.pas 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. unit UnitMain;
  2. interface
  3. uses
  4. Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  5. Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  6. GR32,
  7. GR32_Transforms,
  8. GR32_Rasterizers,
  9. GR32_Image,
  10. GR32_Layers;
  11. type
  12. TFormMain = class(TForm)
  13. Panel1: TPanel;
  14. CheckBoxLive: TCheckBox;
  15. ButtonApply: TButton;
  16. CheckBoxExtrapolate: TCheckBox;
  17. ImageSource: TImage32;
  18. ImageDest: TImage32;
  19. TimerMarchingAnts: TTimer;
  20. TimerUpdate: TTimer;
  21. LabelStats: TLabel;
  22. ButtonReset: TButton;
  23. Label1: TLabel;
  24. ComboBoxRasterizer: TComboBox;
  25. CheckBoxLiveDraft: TCheckBox;
  26. TimerDraft: TTimer;
  27. procedure FormResize(Sender: TObject);
  28. procedure FormShow(Sender: TObject);
  29. procedure TimerMarchingAntsTimer(Sender: TObject);
  30. procedure ButtonApplyClick(Sender: TObject);
  31. procedure TimerUpdateTimer(Sender: TObject);
  32. procedure CheckBoxExtrapolateClick(Sender: TObject);
  33. procedure CheckBoxLiveClick(Sender: TObject);
  34. procedure ButtonResetClick(Sender: TObject);
  35. procedure ComboBoxRasterizerChange(Sender: TObject);
  36. procedure TimerDraftTimer(Sender: TObject);
  37. private type
  38. TSourceDest = (sdSource, sdDest);
  39. private
  40. FTransformation: TProjectiveTransformationEx;
  41. FRasterizer: TRasterizer;
  42. FDraftRasterizer: TRasterizer;
  43. FCurrentRasterizer: TRasterizer;
  44. FLayers: array[TSourceDest] of TPolygonRubberbandLayer;
  45. FCorners: array[TSourceDest] of TFloatQuadrilateral;
  46. FActiveIndex: array[TSourceDest] of integer;
  47. FInvalidIndex: array[TSourceDest] of integer;
  48. private
  49. procedure LayerHandleClicked(Sender: TCustomRubberBandLayer; AIndex: integer);
  50. procedure LayerHandleMove(Sender: TCustomRubberBandLayer; AIndex: integer; var APos: TFloatPoint);
  51. procedure LayerHandlePaint(Sender: TCustomRubberBandLayer; Buffer: TBitmap32; const p: TFloatPoint; AIndex: integer; var ADrawParams: TRubberBandHandleDrawParams; var Handled: boolean);
  52. procedure LayerHandleUpdate(Sender: TCustomRubberBandLayer; Buffer: TBitmap32; const p: TFloatPoint; AIndex: integer; var UpdateRect: TRect; var Handled: boolean);
  53. procedure LayerMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  54. private
  55. function IsCornerValid(const Quad: TFloatQuadrilateral; Index, ActiveIndex: integer): boolean;
  56. function MoveCorner(SourceDest: TSourceDest; var APos: TFloatPoint; ASnap: boolean): boolean;
  57. function SortCorners(SourceDest: TSourceDest): boolean;
  58. procedure UpdateCorners;
  59. public
  60. constructor Create(AOwner: TComponent); override;
  61. destructor Destroy; override;
  62. end;
  63. var
  64. FormMain: TFormMain;
  65. //------------------------------------------------------------------------------
  66. //------------------------------------------------------------------------------
  67. //------------------------------------------------------------------------------
  68. implementation
  69. {$R *.dfm}
  70. uses
  71. System.Math,
  72. System.Types,
  73. System.Diagnostics,
  74. GR32_Geometry,
  75. GR32_Polygons,
  76. GR32_VectorUtils,
  77. GR32.ImageFormats.JPG,
  78. GR32.Examples;
  79. const
  80. // Style and size of first handle
  81. FirstHandleStyle = hsDiamond;
  82. FirstHandleExtraSize = 1;
  83. FirstOutlineWidth = 1.5;
  84. // Style and size of other handles
  85. OtherHandleStyle = hsCircle;
  86. OtherHandleExtraSize = 0;
  87. OtherOutlineWidth = 1.0;
  88. // Handle fill colors
  89. ColorHandleFill: TColor32 = $7FFFFFFF;
  90. ColorHandleActive: TColor32 = $7F007FFF;
  91. ColorHandleError: TColor32 = $FFFF0000;
  92. ColorHandleOutline: TColor32 = $FF00007F;
  93. function RectToPolygon(const r: TFloatRect): TArrayOfFloatPoint;
  94. begin
  95. SetLength(Result, 4);
  96. Result[0].X := r.Left;
  97. Result[0].Y := r.Top;
  98. Result[1].X := r.Right;
  99. Result[1].Y := r.Top;
  100. Result[2].X := r.Right;
  101. Result[2].Y := r.Bottom;
  102. Result[3].X := r.Left;
  103. Result[3].Y := r.Bottom;
  104. end;
  105. //------------------------------------------------------------------------------
  106. constructor TFormMain.Create(AOwner: TComponent);
  107. procedure AddRasterizer(RasterizerClass: TRasterizerClass);
  108. begin
  109. ComboBoxRasterizer.Items.AddObject(RasterizerClass.ClassName, TObject(RasterizerClass));
  110. end;
  111. var
  112. SourceDest: TSourceDest;
  113. begin
  114. inherited;
  115. ImageSource.Bitmap.LoadFromFile(Graphics32Examples.MediaFolder + '\Notre Dame.jpg');
  116. // Use Nearest resampler for the source so we can see the individual pixels when zoomed
  117. ImageSource.Bitmap.ResamplerClassName := 'TNearestResampler';
  118. ImageDest.Bitmap.Assign(ImageSource.Bitmap);
  119. ImageDest.Bitmap.ResamplerClassName := 'TLinearResampler';
  120. ImageSource.Scale := 0.5;
  121. ImageDest.Scale := 0.5;
  122. FLayers[sdSource] := ImageSource.Layers.Add<TPolygonRubberbandLayer>;
  123. FLayers[sdDest] := ImageDest.Layers.Add<TPolygonRubberbandLayer>;
  124. for SourceDest := Low(TSourceDest) to High(TSourceDest) do
  125. begin
  126. FLayers[SourceDest].Scaled := True;
  127. FLayers[SourceDest].Cursor := crSizeAll;
  128. FLayers[SourceDest].FrameStipple := [clWhite32, clWhite32, clWhite32, clWhite32, clBlack32, clBlack32, clBlack32, clBlack32];
  129. FLayers[SourceDest].HandleSize := 5;
  130. FLayers[SourceDest].OnHandleClicked := LayerHandleClicked;
  131. FLayers[SourceDest].OnHandleMove := LayerHandleMove;
  132. FLayers[SourceDest].OnMouseUp := LayerMouseUp;
  133. FLayers[SourceDest].OnPaintHandle := LayerHandlePaint;
  134. FLayers[SourceDest].OnUpdateHandle := LayerHandleUpdate;
  135. FActiveIndex[SourceDest] := -1;
  136. FInvalidIndex[SourceDest] := -1;
  137. end;
  138. FTransformation := TProjectiveTransformationEx.Create;
  139. FDraftRasterizer := TDraftRasterizer.Create;
  140. AddRasterizer(TRegularRasterizer);
  141. AddRasterizer(TThreadRegularRasterizer);
  142. {$if declared(TParallelRegularRasterizer)}
  143. AddRasterizer(TParallelRegularRasterizer);
  144. {$ifend}
  145. {$if declared(TTaskRegularRasterizer)}
  146. AddRasterizer(TTaskRegularRasterizer);
  147. {$ifend}
  148. ComboBoxRasterizer.ItemIndex := 0;
  149. ComboBoxRasterizerChange(nil);
  150. end;
  151. destructor TFormMain.Destroy;
  152. begin
  153. FTransformation.Free;
  154. FRasterizer.Free;
  155. FDraftRasterizer.Free;
  156. inherited;
  157. end;
  158. //------------------------------------------------------------------------------
  159. procedure TFormMain.FormResize(Sender: TObject);
  160. begin
  161. ImageSource.Width := ClientWidth div 2;
  162. // Center bitmap in viewport.
  163. // The reason we don't just use BitmapAlign=baCenter is that
  164. // we would also like to be able to pan the image with the mouse.
  165. ImageSource.ScrollToCenter;
  166. ImageDest.ScrollToCenter;
  167. end;
  168. procedure TFormMain.FormShow(Sender: TObject);
  169. var
  170. Points: TArrayOfFloatPoint;
  171. begin
  172. Points := BuildPolygonF([250.25, 45.25, 537.25, 49, 720, 532.5, 52.5, 532.5]);
  173. // Translate vertices so they are relative to bitmap
  174. FLayers[sdSource].Vertices := TranslatePolygon(Points, ImageSource.OffsetHorz, ImageSource.OffsetVert);
  175. FLayers[sdDest].Location := FloatRect(ImageDest.GetBitmapRect);
  176. Points := BuildPolygonF([252, 50, 534, 50, 534, 529, 252, 529]);
  177. // Translate vertices so they are relative to bitmap
  178. FLayers[sdDest].Vertices := TranslatePolygon(Points, ImageDest.OffsetHorz, ImageDest.OffsetVert);
  179. UpdateCorners;
  180. end;
  181. procedure TFormMain.ButtonResetClick(Sender: TObject);
  182. begin
  183. // Layer location doesn't really matter for rubber band layers.
  184. // Handles/vertices are relative to bitmap.
  185. FLayers[sdSource].Location := FloatRect(ImageSource.Bitmap.BoundsRect);
  186. FLayers[sdSource].Vertices := RectToPolygon(ImageSource.Bitmap.BoundsRect);
  187. FActiveIndex[sdSource] := -1;
  188. FInvalidIndex[sdSource] := -1;
  189. FLayers[sdDest].Location := FloatRect(ImageDest.Bitmap.BoundsRect);
  190. FLayers[sdDest].Vertices := RectToPolygon(ImageDest.Bitmap.BoundsRect);
  191. FActiveIndex[sdDest] := -1;
  192. FInvalidIndex[sdDest] := -1;
  193. UpdateCorners;
  194. ButtonApply.Click;
  195. end;
  196. procedure TFormMain.CheckBoxExtrapolateClick(Sender: TObject);
  197. begin
  198. if (CheckBoxLive.State in [cbChecked, cbGrayed]) then
  199. ButtonApply.Click;
  200. end;
  201. procedure TFormMain.CheckBoxLiveClick(Sender: TObject);
  202. begin
  203. if (CheckBoxLive.State in [cbChecked, cbGrayed]) then
  204. ButtonApply.Click;
  205. end;
  206. procedure TFormMain.ComboBoxRasterizerChange(Sender: TObject);
  207. begin
  208. FreeAndNil(FRasterizer);
  209. FRasterizer := TRasterizerClass(ComboBoxRasterizer.Items.Objects[ComboBoxRasterizer.ItemIndex]).Create;
  210. FCurrentRasterizer := FRasterizer;
  211. end;
  212. //------------------------------------------------------------------------------
  213. procedure TFormMain.LayerHandleClicked(Sender: TCustomRubberBandLayer; AIndex: integer);
  214. var
  215. SourceDest: TSourceDest;
  216. begin
  217. if (Sender = FLayers[sdSource]) then
  218. SourceDest := sdSource
  219. else
  220. SourceDest := sdDest;
  221. FActiveIndex[SourceDest] := AIndex;
  222. Sender.Update;
  223. end;
  224. procedure TFormMain.LayerHandleMove(Sender: TCustomRubberBandLayer; AIndex: integer; var APos: TFloatPoint);
  225. var
  226. SourceDest: TSourceDest;
  227. i: integer;
  228. Snap: boolean;
  229. HitTestVertex: ILayerHitTestVertex;
  230. begin
  231. if (Sender = FLayers[sdSource]) then
  232. SourceDest := sdSource
  233. else
  234. SourceDest := sdDest;
  235. (*
  236. ** Moving a handle
  237. *)
  238. if (AIndex <> -1) then
  239. begin
  240. Snap := (ssShift in Sender.ActiveHitTest.Shift);
  241. if (not MoveCorner(SourceDest, APos, Snap)) then
  242. exit;
  243. if (SortCorners(SourceDest)) then
  244. begin
  245. // Corners has been reordered; Update vertices and hittest
  246. for i := Low(FCorners[SourceDest]) to High(FCorners[SourceDest]) do
  247. FLayers[SourceDest].Vertex[i] := FCorners[SourceDest, i];
  248. if Supports(Sender.ActiveHitTest, ILayerHitTestVertex, HitTestVertex) then
  249. HitTestVertex.Vertex := FActiveIndex[SourceDest];
  250. end;
  251. // Determine if polygon is convex; Mark the invalid vertex if it isn't
  252. FInvalidIndex[SourceDest] := -1;
  253. for i := Low(FCorners[SourceDest]) to High(FCorners[SourceDest]) do
  254. if (not IsCornerValid(FCorners[SourceDest], i, FActiveIndex[SourceDest])) then
  255. begin
  256. FInvalidIndex[SourceDest] := i;
  257. Sender.Update;
  258. break;
  259. end;
  260. end else
  261. (*
  262. ** Moving layer
  263. *)
  264. begin
  265. UpdateCorners;
  266. end;
  267. // If draft rasterization is enabled then use fast but ugly rasterizer during move/drag
  268. // and queue quality rasterize for later
  269. if (CheckBoxLive.State <> cbUnchecked) and (CheckBoxLiveDraft.Checked) then
  270. begin
  271. FCurrentRasterizer := FDraftRasterizer;
  272. TimerDraft.Enabled := False;
  273. TimerDraft.Enabled := True;
  274. end else
  275. FCurrentRasterizer := FRasterizer;
  276. // Semi-live; Defer update until user pauses movement
  277. if (CheckBoxLive.State = cbGrayed) then
  278. begin
  279. TimerUpdate.Enabled := False;
  280. TimerUpdate.Enabled := True;
  281. end;
  282. // Live; Update immediately
  283. if (CheckBoxLive.State = cbChecked) then
  284. ButtonApply.Click;
  285. end;
  286. procedure TFormMain.LayerHandlePaint(Sender: TCustomRubberBandLayer; Buffer: TBitmap32; const p: TFloatPoint; AIndex: integer;
  287. var ADrawParams: TRubberBandHandleDrawParams; var Handled: boolean);
  288. var
  289. SourceDest: TSourceDest;
  290. begin
  291. if (AIndex = -1) then
  292. exit;
  293. if (AIndex = 0) then
  294. begin
  295. ADrawParams.HandleStyle := FirstHandleStyle;
  296. ADrawParams.HandleSize := ADrawParams.HandleSize + FirstHandleExtraSize;
  297. ADrawParams.HandleFrameSize := FirstOutlineWidth;
  298. end else
  299. begin
  300. ADrawParams.HandleStyle := OtherHandleStyle;
  301. ADrawParams.HandleSize := ADrawParams.HandleSize + OtherHandleExtraSize;
  302. ADrawParams.HandleFrameSize := OtherOutlineWidth;
  303. end;
  304. if (Sender = FLayers[sdSource]) then
  305. SourceDest := sdSource
  306. else
  307. SourceDest := sdDest;
  308. if (AIndex = FInvalidIndex[SourceDest]) then
  309. ADrawParams.HandleFill := ColorHandleError
  310. else
  311. if (AIndex = FActiveIndex[SourceDest]) then
  312. ADrawParams.HandleFill := ColorHandleActive
  313. else
  314. ADrawParams.HandleFill := ColorHandleFill;
  315. ADrawParams.HandleFrame := ColorHandleOutline;
  316. end;
  317. procedure TFormMain.LayerHandleUpdate(Sender: TCustomRubberBandLayer; Buffer: TBitmap32; const p: TFloatPoint; AIndex: integer;
  318. var UpdateRect: TRect; var Handled: boolean);
  319. var
  320. HandleRect: TFloatRect;
  321. HandleSize: Single;
  322. begin
  323. // Since we alter the handle size in the handle paint event handler we also need to
  324. // alter the update rect correspondingly.
  325. HandleSize := Sender.HandleSize + Max(FirstOutlineWidth, OtherOutlineWidth) + Max(FirstHandleExtraSize, OtherHandleExtraSize);
  326. HandleRect.TopLeft := p;
  327. HandleRect.BottomRight := HandleRect.TopLeft;
  328. HandleRect.Inflate(HandleSize, HandleSize);
  329. UpdateRect := MakeRect(HandleRect, rrOutside);
  330. end;
  331. procedure TFormMain.LayerMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  332. var
  333. SourceDest: TSourceDest;
  334. begin
  335. if (Sender = FLayers[sdSource]) then
  336. SourceDest := sdSource
  337. else
  338. SourceDest := sdDest;
  339. FActiveIndex[SourceDest] := -1;
  340. FLayers[SourceDest].Update;
  341. end;
  342. //------------------------------------------------------------------------------
  343. procedure TFormMain.TimerMarchingAntsTimer(Sender: TObject);
  344. begin
  345. if (FLayers[sdSource].ActiveHitTest <> nil) then
  346. FLayers[sdSource].FrameStippleCounter := FLayers[sdSource].FrameStippleCounter + 1.5;
  347. if (FLayers[sdDest].ActiveHitTest <> nil) then
  348. FLayers[sdDest].FrameStippleCounter := FLayers[sdDest].FrameStippleCounter + 1.5;
  349. end;
  350. procedure TFormMain.TimerUpdateTimer(Sender: TObject);
  351. begin
  352. TimerUpdate.Enabled := False;
  353. ButtonApply.Click;
  354. end;
  355. procedure TFormMain.TimerDraftTimer(Sender: TObject);
  356. begin
  357. TimerDraft.Enabled := False;
  358. FCurrentRasterizer := FRasterizer;
  359. ButtonApply.Click;
  360. end;
  361. //------------------------------------------------------------------------------
  362. function TFormMain.IsCornerValid(const Quad: TFloatQuadrilateral; Index, ActiveIndex: integer): boolean;
  363. var
  364. p0, p2: integer;
  365. v1, v2: TFloatPoint;
  366. Angle: TFloat;
  367. begin
  368. p0 := Index-1;
  369. p2 := Index+1;
  370. if (p0 < 0) then
  371. p0 := 3;
  372. if (p2 > 3) then
  373. p2 := 0;
  374. // Translate to origin
  375. v1 := Quad[p0] - Quad[Index];
  376. v2 := Quad[p2] - Quad[Index];
  377. Angle := RadToDeg(ArcTan2(CrossProduct(v1, v2), Dot(v1, v2)));
  378. if (Index = ActiveIndex) then
  379. Angle := 1.0 * Angle;
  380. Result := (Angle < 0);
  381. end;
  382. //------------------------------------------------------------------------------
  383. function TFormMain.MoveCorner(SourceDest: TSourceDest; var APos: TFloatPoint; ASnap: boolean): boolean;
  384. function CrossProduct(p, A, B: TFloatPoint): TFloat;
  385. begin
  386. Result := (p.X - A.X) * (B.Y - A.Y) - (p.Y - A.Y) * (B.X - A.X);
  387. end;
  388. var
  389. OppositePos: TFloatPoint;
  390. PrevPos: TFloatPoint;
  391. NextPos: TFloatPoint;
  392. Cross: TFloat;
  393. begin
  394. if (ASnap) then
  395. begin
  396. // Snap to 90 degree angle
  397. OppositePos := FCorners[SourceDest, (FActiveIndex[SourceDest]+2) mod 4];
  398. PrevPos := FCorners[SourceDest, (FActiveIndex[SourceDest]+4-1) mod 4];
  399. NextPos := FCorners[SourceDest, (FActiveIndex[SourceDest]+1) mod 4];
  400. // Find snap point that is on the opposite side of the opposite corner
  401. // Find (AB x AC) where A and B are the prev and next corners and C is the opposite corner
  402. Cross := CrossProduct(OppositePos, PrevPos, NextPos);
  403. // Create one of the two possible candidate points...
  404. APos.X := PrevPos.X;
  405. APos.Y := NextPos.Y;
  406. // ...and ensure that the cross product has the opposite sign.
  407. if (Sign(CrossProduct(APos, PrevPos, NextPos)) = Sign(Cross)) then
  408. begin
  409. // Our first attempt was on the same side. Use the other candidate instead.
  410. APos.X := NextPos.X;
  411. APos.Y := PrevPos.Y;
  412. end;
  413. end;
  414. Result := (APos <> FCorners[SourceDest, FActiveIndex[SourceDest]]);
  415. if (Result) then
  416. FCorners[SourceDest, FActiveIndex[SourceDest]] := APos;
  417. end;
  418. function TFormMain.SortCorners(SourceDest: TSourceDest): boolean;
  419. // Given three colinear points p, a, b, the function checks if
  420. // point p lies on line segment ab
  421. function OnSegment(const a, b, p: TFloatPoint): boolean;
  422. begin
  423. Result := (p.X <= Max(a.X, b.X)) and (p.X >= Min(a.X, b.X)) and (p.Y <= Max(a.Y, b.Y)) and (p.Y >= Min(a.Y, b.Y));
  424. end;
  425. // Given the ordered triplet (a, b, c), the function returns
  426. // the following values:
  427. // 0: a, b and c are colinear
  428. // 1: abc is clockwise
  429. // -1: abc is counterclockwise
  430. // https://www.geeksforgeeks.org/orientation-3-ordered-points/
  431. function Orientation(const a, b, c: TFloatPoint): integer;
  432. begin
  433. Result := Sign((b.Y - a.Y) * (c.X - b.X) - (b.X - a.X) * (c.Y - b.Y));
  434. end;
  435. function Intersect(const a, b, c, d: TFloatPoint): boolean;
  436. var
  437. o1, o2, o3, o4: integer;
  438. begin
  439. o1 := Orientation(a, b, c);
  440. o2 := Orientation(a, b, d);
  441. o3 := Orientation(c, d, a);
  442. o4 := Orientation(c, d, b);
  443. // General case
  444. if (o1 <> o2) and (o3 <> o4) then
  445. Exit(True);
  446. // Special Cases
  447. // a, b and c are colinear and c lies on segment ab
  448. if (o1 = 0) and (OnSegment(a, b, c)) then
  449. Exit(True);
  450. // a, b and d are colinear and d lies on segment ab
  451. if (o2 = 0) and (OnSegment(a, b, d)) then
  452. Exit(True);
  453. // c, d and a are colinear and a lies on segment cd
  454. if (o3 = 0) and (OnSegment(c, d, a)) then
  455. Exit(True);
  456. // c, d and b are colinear and b lies on segment cd
  457. if (o4 = 0) and (OnSegment(c, d, b)) then
  458. Exit(True);
  459. Result := False;
  460. end;
  461. procedure Swap(a, b: integer);
  462. var
  463. n: TFloatPoint;
  464. begin
  465. n := FCorners[SourceDest, a];
  466. FCorners[SourceDest, a] := FCorners[SourceDest, b];
  467. FCorners[SourceDest, b] := n;
  468. if (FActiveIndex[SourceDest] = a) then
  469. FActiveIndex[SourceDest] := b
  470. else
  471. if (FActiveIndex[SourceDest] = b) then
  472. FActiveIndex[SourceDest] := a;
  473. if (FInvalidIndex[SourceDest] = a) then
  474. FInvalidIndex[SourceDest] := b
  475. else
  476. if (FInvalidIndex[SourceDest] = b) then
  477. FInvalidIndex[SourceDest] := a;
  478. end;
  479. procedure SortClockwise;
  480. begin
  481. if (Orientation(FCorners[SourceDest, 0], FCorners[SourceDest, 1], FCorners[SourceDest, 2]) < 0) then
  482. begin
  483. // Triangle abc is already clockwise. Where does d fit?
  484. if (Orientation(FCorners[SourceDest, 0], FCorners[SourceDest, 2], FCorners[SourceDest, 3]) < 0) then
  485. Exit;
  486. if (Orientation(FCorners[SourceDest, 0], FCorners[SourceDest, 1], FCorners[SourceDest, 3]) < 0) then
  487. Swap(2, 3)
  488. else
  489. Swap(0, 3)
  490. end else
  491. if (Orientation(FCorners[SourceDest, 0], FCorners[SourceDest, 2], FCorners[SourceDest, 3]) < 0) then
  492. begin
  493. // Triangle abc is counterclockwise, i.e. acb is clockwise.
  494. // Also, acd is clockwise.
  495. if (Orientation(FCorners[SourceDest, 0], FCorners[SourceDest, 1], FCorners[SourceDest, 3]) < 0) then
  496. Swap(1, 2)
  497. else
  498. Swap(0, 1);
  499. end else
  500. // Triangle abc is counterclockwise, and acd is counterclockwise.
  501. // Therefore, abcd is counterclockwise.
  502. Swap(0, 2);
  503. Result := True;
  504. end;
  505. procedure FindTopLeft;
  506. var
  507. MinSum: TFLoat;
  508. MinIndex: integer;
  509. i: integer;
  510. Temp: TFloatQuadrilateral;
  511. begin
  512. MinSum := FCorners[SourceDest, 0].X + FCorners[SourceDest, 0].Y;
  513. MinIndex := 0;
  514. for i := 1 to 3 do
  515. begin
  516. var Sum := FCorners[SourceDest, i].X + FCorners[SourceDest, i].Y;
  517. if (Sum < MinSum) then
  518. begin
  519. MinSum := Sum;
  520. MinIndex := i;
  521. end else
  522. if (Sum = MinSum) and (FCorners[SourceDest, i].X < FCorners[SourceDest, MinIndex].X) then
  523. MinIndex := i;
  524. end;
  525. if (MinIndex = 0) then
  526. Exit;
  527. Temp := FCorners[SourceDest];
  528. for i := 0 to 3 do
  529. Temp[i] := FCorners[SourceDest, (MinIndex+i) mod 4];
  530. FCorners[SourceDest] := Temp;
  531. if (FActiveIndex[SourceDest] <> -1) then
  532. FActiveIndex[SourceDest] := (FActiveIndex[SourceDest] - MinIndex + 4) mod 4;
  533. if (FInvalidIndex[SourceDest] <> -1) then
  534. FInvalidIndex[SourceDest] := (FInvalidIndex[SourceDest] - MinIndex + 4) mod 4;
  535. Result := True;
  536. end;
  537. begin
  538. Result := False;
  539. (*
  540. ** Order points so they appear in the array in clockwise order
  541. ** (i.e. moving along the vertices we always turn right).
  542. **
  543. ** Furthermore we would like to first point to be top-left-ish.
  544. **
  545. ** Remember that we use the bitmap coordinate system where Y is reversed.
  546. **
  547. ** X
  548. ** +------------>
  549. ** |
  550. ** Y | A--B
  551. ** | | |
  552. ** | D--C
  553. ** |
  554. ** V
  555. **
  556. ** Clockwise Sort algorithm based on:
  557. ** - https://stackoverflow.com/a/245079/2249664
  558. ** - https://stackoverflow.com/a/246063/2249664
  559. **
  560. ** Note that this sort algorithm only works if the polygon is convex.
  561. *)
  562. SortClockwise;
  563. FindTopLeft;
  564. end;
  565. //------------------------------------------------------------------------------
  566. procedure TFormMain.UpdateCorners;
  567. var
  568. SourceDest: TSourceDest;
  569. i: integer;
  570. begin
  571. for SourceDest := Low(TSourceDest) to High(TSourceDest) do
  572. for i := Low(FCorners[SourceDest]) to High(FCorners[SourceDest]) do
  573. FCorners[SourceDest, i] := FLayers[SourceDest].Vertex[i];
  574. end;
  575. //------------------------------------------------------------------------------
  576. procedure TFormMain.ButtonApplyClick(Sender: TObject);
  577. var
  578. SourceDest: TSourceDest;
  579. i: integer;
  580. StopWatch: TStopWatch;
  581. begin
  582. UpdateCorners;
  583. for SourceDest := Low(TSourceDest) to High(TSourceDest) do
  584. begin
  585. // Ensure that corners are stored clockwise, with first point top/left-most.
  586. // This enables us to do something sensible with the quad even if the user has
  587. // messed up the order. Unfortunately it also means that the user can't mirror
  588. // by reversing the quad on purpose.
  589. if (SortCorners(SourceDest)) then
  590. for i := Low(FCorners[SourceDest]) to High(FCorners[SourceDest]) do
  591. FLayers[SourceDest].Vertex[i] := FCorners[SourceDest, i];
  592. end;
  593. FTransformation.SourceQuad := FCorners[sdSource];
  594. FTransformation.DestQuad := FCorners[sdDest];
  595. FTransformation.Extrapolate := CheckBoxExtrapolate.Checked;
  596. ImageDest.Bitmap.BeginUpdate;
  597. try
  598. ImageDest.Bitmap.Clear(0);
  599. FTransformation.SrcRect := FloatRect(ImageSource.Bitmap.BoundsRect);
  600. StopWatch := TStopWatch.StartNew;
  601. Transform(ImageDest.Bitmap, ImageSource.Bitmap, FTransformation, FCurrentRasterizer, False); // Forward projection
  602. StopWatch.Stop;
  603. LabelStats.Caption := Format('Rasterized in %d mS', [StopWatch.ElapsedMilliseconds]);
  604. finally
  605. ImageDest.Bitmap.EndUpdate;
  606. end;
  607. Update;
  608. end;
  609. end.