Img32.Transform.pas 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375
  1. unit Img32.Transform;
  2. (*******************************************************************************
  3. * Author : Angus Johnson *
  4. * Version : 4.7 *
  5. * Date : 6 January 2025 *
  6. * Website : http://www.angusj.com *
  7. * Copyright : Angus Johnson 2019-2025 *
  8. * Purpose : Affine and projective transformation routines for TImage32 *
  9. * License : http://www.boost.org/LICENSE_1_0.txt *
  10. *******************************************************************************)
  11. interface
  12. {$I Img32.inc}
  13. uses
  14. SysUtils, Classes, Math, Types, Img32, Img32.Vector;
  15. type
  16. TMatrixD = array [0..2, 0..2] of double;
  17. //Matrix functions
  18. function IsIdentityMatrix(const matrix: TMatrixD): Boolean;
  19. {$IFDEF INLINE} inline; {$ENDIF}
  20. function IsValidMatrix(const matrix: TMatrixD): Boolean;
  21. {$IFDEF INLINE} inline; {$ENDIF}
  22. function Matrix(const m00, m01, m02, m10, m11, m12, m20, m21, m22: double): TMatrixD;
  23. function MatrixDeterminant(const matrix: TMatrixD): double;
  24. function MatrixAdjugate(const matrix: TMatrixD): TMatrixD;
  25. function MatrixInvert(var matrix: TMatrixD): Boolean;
  26. // Note: Matrix multiplication IS NOT commutative hence ...
  27. procedure MatrixMultiply(var matrix1: TMatrixD; const matrix2: TMatrixD);
  28. procedure MatrixMultiply2(const matrix1: TMatrixD; var matrix2: TMatrixD);
  29. procedure MatrixApply(const matrix: TMatrixD;
  30. var x, y: double); overload; {$IFDEF INLINE} inline; {$ENDIF}
  31. procedure MatrixApply(const matrix: TMatrixD;
  32. var pt: TPointD); overload; {$IFDEF INLINE} inline; {$ENDIF}
  33. procedure MatrixApply(const matrix: TMatrixD; var rec: TRect); overload;
  34. procedure MatrixApply(const matrix: TMatrixD; var rec: TRectD); overload;
  35. procedure MatrixApply(const matrix: TMatrixD; var path: TPathD); overload;
  36. procedure MatrixApply(const matrix: TMatrixD; var paths: TPathsD); overload;
  37. procedure MatrixApply(const matrix: TMatrixD;
  38. img: TImage32; scaleAdjust: Boolean = false); overload; {$IFDEF INLINE} inline; {$ENDIF}
  39. procedure MatrixApply(const matrix: TMatrixD;
  40. img, targetImg: TImage32; scaleAdjust: Boolean = false); overload; {$IFDEF INLINE} inline; {$ENDIF}
  41. procedure MatrixSkew(var matrix: TMatrixD; angleX, angleY: double);
  42. procedure MatrixScale(var matrix: TMatrixD; scale: double); overload;
  43. procedure MatrixScale(var matrix: TMatrixD; scaleX, scaleY: double); overload;
  44. procedure MatrixRotate(var matrix: TMatrixD; angRad: double); overload;
  45. procedure MatrixRotate(var matrix: TMatrixD; const center: TPointD; angRad: double); overload;
  46. procedure MatrixTranslate(var matrix: TMatrixD; dx, dy: double);
  47. // The following MatrixExtract routines assume here is no skew
  48. procedure MatrixExtractScale(const mat: TMatrixD; out scale: double); overload;
  49. procedure MatrixExtractScale(const mat: TMatrixD; out X, Y: double); overload;
  50. procedure MatrixExtractTranslation(const mat: TMatrixD; out dx, dy: double);
  51. procedure MatrixExtractRotation(const mat: TMatrixD; out angle: double);
  52. // MatrixExtractAll - except skew :)
  53. function MatrixExtractAll(const mat: TMatrixD; out angle: double;
  54. out scale, trans: TPointD): Boolean;
  55. // AffineTransformImage: will automagically translate the image
  56. // Note: when the "scaleAdjust" parameter is enabled, it prevents antialiasing
  57. // from extending way outside of images when they are being enlarged
  58. // significantly (> 2 times) and rotated concurrently
  59. function AffineTransformImage(img: TImage32; matrix: TMatrixD;
  60. scaleAdjust: Boolean = false): TPoint; overload; {$IFDEF INLINE} inline; {$ENDIF}
  61. function AffineTransformImage(img, targetImg: TImage32; matrix: TMatrixD;
  62. scaleAdjust: Boolean = false): TPoint; overload;
  63. // ProjectiveTransform:
  64. // srcPts, dstPts => each path must contain 4 points
  65. // margins => the margins around dstPts (in the dest. projective).
  66. // Margins are only meaningful when srcPts are inside the image.
  67. function ProjectiveTransform(img: TImage32;
  68. const srcPts, dstPts: TPathD; const margins: TRect): Boolean; overload; {$IFDEF INLINE} inline; {$ENDIF}
  69. function ProjectiveTransform(img, targetImg: TImage32;
  70. const srcPts, dstPts: TPathD; const margins: TRect): Boolean; overload;
  71. function SplineVertTransform(img: TImage32; const topSpline: TPathD;
  72. splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean; overload; {$IFDEF INLINE} inline; {$ENDIF}
  73. function SplineVertTransform(img, targetImg: TImage32; const topSpline: TPathD;
  74. splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean; overload;
  75. function SplineHorzTransform(img: TImage32; const leftSpline: TPathD;
  76. splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean; overload; {$IFDEF INLINE} inline; {$ENDIF}
  77. function SplineHorzTransform(img, targetImg: TImage32; const leftSpline: TPathD;
  78. splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean; overload;
  79. type
  80. PWeightedColor = ^TWeightedColor;
  81. TWeightedColor = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
  82. private
  83. fAddCount : Integer;
  84. fAlphaTot : Int64;
  85. fColorTotR: Int64;
  86. fColorTotG: Int64;
  87. fColorTotB: Int64;
  88. function GetColor: TColor32;
  89. public
  90. procedure Reset; overload; {$IFDEF INLINE} inline; {$ENDIF}
  91. procedure Reset(c: TColor32; w: Integer = 1); overload; {$IFDEF INLINE} inline; {$ENDIF}
  92. procedure Add(c: TColor32; w: Integer); overload; {$IFDEF INLINE_COMPATIBLE} inline; {$ENDIF}
  93. procedure Add(c: TColor32); overload; {$IFDEF INLINE} inline; {$ENDIF}
  94. procedure Add(const other: TWeightedColor); overload;
  95. {$IFDEF INLINE} inline; {$ENDIF}
  96. procedure Subtract(c: TColor32; w: Integer); overload;
  97. procedure Subtract(c: TColor32); overload; {$IFDEF INLINE} inline; {$ENDIF}
  98. procedure Subtract(const other: TWeightedColor); overload;
  99. {$IFDEF INLINE} inline; {$ENDIF}
  100. function AddSubtract(addC, subC: TColor32): Boolean; {$IFDEF INLINE_COMPATIBLE} inline; {$ENDIF}
  101. function AddNoneSubtract(c: TColor32): Boolean; {$IFDEF INLINE_COMPATIBLE} inline; {$ENDIF}
  102. procedure AddWeight(w: Integer); {$IFDEF INLINE} inline; {$ENDIF}
  103. property AddCount: Integer read fAddCount;
  104. property Color: TColor32 read GetColor;
  105. property Weight: integer read fAddCount;
  106. end;
  107. TArrayOfWeightedColor = array of TWeightedColor;
  108. const
  109. IdentityMatrix: TMatrixD = ((1, 0, 0),(0, 1, 0),(0, 0, 1));
  110. implementation
  111. uses Img32.Resamplers;
  112. resourcestring
  113. rsInvalidScale = 'Invalid matrix scaling factor (0)';
  114. const
  115. DivOneByXTableSize = 1024;
  116. {$IFDEF CPUX86}
  117. // Use faster Trunc for x86 code in this unit.
  118. Trunc: function(Value: Double): Integer = __Trunc;
  119. {$ENDIF CPUX86}
  120. var
  121. // DivOneByXTable[x] = 1/x
  122. DivOneByXTable: array[0 .. DivOneByXTableSize -1] of Double;
  123. //------------------------------------------------------------------------------
  124. // Matrix functions
  125. //------------------------------------------------------------------------------
  126. function IsIdentityMatrix(const matrix: TMatrixD): Boolean;
  127. begin
  128. result := (matrix[0,0] = 1) and (matrix[0,1] = 0) and (matrix[0,2] = 0) and
  129. (matrix[1,0] = 0) and (matrix[1,1] = 1) and (matrix[1,2] = 0) and
  130. (matrix[2,0] = 0) and (matrix[2,1] = 0) and (matrix[2,2] = 1);
  131. end;
  132. //------------------------------------------------------------------------------
  133. function IsValidMatrix(const matrix: TMatrixD): Boolean;
  134. begin
  135. result := matrix[2][2] = 1.0;
  136. end;
  137. //------------------------------------------------------------------------------
  138. function Matrix(const m00, m01, m02, m10, m11, m12, m20, m21, m22: double): TMatrixD;
  139. begin
  140. Result[0,0] := m00; Result[0,1] := m01; Result[0,2] := m02;
  141. Result[1,0] := m10; Result[1,1] := m11; Result[1,2] := m12;
  142. Result[2,0] := m20; Result[2,1] := m21; Result[2,2] := m22;
  143. end;
  144. //------------------------------------------------------------------------------
  145. function Det4(a1, a2, b1, b2: double): double; {$IFDEF INLINE} inline; {$ENDIF}
  146. begin
  147. Result := a1 * b2 - a2 * b1;
  148. end;
  149. //------------------------------------------------------------------------------
  150. function Det9(a1, a2, a3, b1, b2, b3, c1, c2, c3: double): double;
  151. {$IFDEF INLINE} inline; {$ENDIF}
  152. begin
  153. Result := a1 * Det4(b2, b3, c2, c3) -
  154. b1 * Det4(a2, a3, c2, c3) +
  155. c1 * Det4(a2, a3, b2, b3);
  156. end;
  157. //------------------------------------------------------------------------------
  158. function MatrixDeterminant(const matrix: TMatrixD): double;
  159. {$IFDEF INLINE} inline; {$ENDIF}
  160. begin
  161. Result := Det9(matrix[0,0], matrix[1,0], matrix[2,0],
  162. matrix[0,1], matrix[1,1], matrix[2,1],
  163. matrix[0,2], matrix[1,2], matrix[2,2]);
  164. end;
  165. //------------------------------------------------------------------------------
  166. function MatrixAdjugate(const matrix: TMatrixD): TMatrixD;
  167. begin
  168. //https://en.wikipedia.org/wiki/Adjugate_matrix
  169. Result[0,0] := Det4(matrix[1,1], matrix[1,2], matrix[2,1], matrix[2,2]);
  170. Result[0,1] := -Det4(matrix[0,1], matrix[0,2], matrix[2,1], matrix[2,2]);
  171. Result[0,2] := Det4(matrix[0,1], matrix[0,2], matrix[1,1], matrix[1,2]);
  172. Result[1,0] := -Det4(matrix[1,0], matrix[1,2], matrix[2,0], matrix[2,2]);
  173. Result[1,1] := Det4(matrix[0,0], matrix[0,2], matrix[2,0], matrix[2,2]);
  174. Result[1,2] := -Det4(matrix[0,0], matrix[0,2], matrix[1,0], matrix[1,2]);
  175. Result[2,0] := Det4(matrix[1,0], matrix[1,1], matrix[2,0], matrix[2,1]);
  176. Result[2,1] := -Det4(matrix[0,0], matrix[0,1], matrix[2,0], matrix[2,1]);
  177. Result[2,2] := Det4(matrix[0,0], matrix[0,1], matrix[1,0], matrix[1,1]);
  178. end;
  179. //------------------------------------------------------------------------------
  180. procedure MatrixApply(const matrix: TMatrixD; var x, y: double);
  181. var
  182. tmpX: double;
  183. begin
  184. tmpX := x;
  185. x := tmpX * matrix[0, 0] + y * matrix[1, 0] + matrix[2, 0];
  186. y := tmpX * matrix[0, 1] + y * matrix[1, 1] + matrix[2, 1];
  187. end;
  188. //------------------------------------------------------------------------------
  189. procedure MatrixApply(const matrix: TMatrixD; var pt: TPointD);
  190. var
  191. tmpX: double;
  192. begin
  193. tmpX := pt.x;
  194. pt.X := tmpX * matrix[0, 0] + pt.Y * matrix[1, 0] + matrix[2, 0];
  195. pt.Y := tmpX * matrix[0, 1] + pt.Y * matrix[1, 1] + matrix[2, 1];
  196. end;
  197. //------------------------------------------------------------------------------
  198. procedure MatrixApply(const matrix: TMatrixD; var rec: TRect);
  199. var
  200. path: TPathD;
  201. begin
  202. if not IsValidMatrix(matrix) then Exit;
  203. path := Rectangle(rec);
  204. MatrixApply(matrix, path);
  205. rec := GetBounds(path);
  206. end;
  207. //------------------------------------------------------------------------------
  208. procedure MatrixApply(const matrix: TMatrixD; var rec: TRectD);
  209. var
  210. path: TPathD;
  211. begin
  212. if not IsValidMatrix(matrix) then Exit;
  213. path := Rectangle(rec);
  214. MatrixApply(matrix, path);
  215. rec := GetBoundsD(path);
  216. end;
  217. //------------------------------------------------------------------------------
  218. procedure MatrixApply(const matrix: TMatrixD; var path: TPathD);
  219. var
  220. i, len: integer;
  221. tmpX: double;
  222. pp: PPointD;
  223. begin
  224. len := Length(path);
  225. if (len = 0) or IsIdentityMatrix(matrix) or
  226. not IsValidMatrix(matrix) then Exit;
  227. pp := @path[0];
  228. for i := 0 to len -1 do
  229. begin
  230. tmpX := pp.X;
  231. pp.X := tmpX * matrix[0, 0] + pp.Y * matrix[1, 0] + matrix[2, 0];
  232. pp.Y := tmpX * matrix[0, 1] + pp.Y * matrix[1, 1] + matrix[2, 1];
  233. inc(pp);
  234. end;
  235. end;
  236. //------------------------------------------------------------------------------
  237. procedure MatrixApply(const matrix: TMatrixD; var paths: TPathsD);
  238. var
  239. i,j,len: integer;
  240. tmpX: double;
  241. pp: PPointD;
  242. begin
  243. if not Assigned(paths) or IsIdentityMatrix(matrix) or
  244. not IsValidMatrix(matrix) then Exit;
  245. for i := 0 to High(paths) do
  246. begin
  247. len := Length(paths[i]);
  248. if len = 0 then Continue;
  249. pp := @paths[i][0];
  250. for j := 0 to High(paths[i]) do
  251. begin
  252. tmpX := pp.X;
  253. pp.X := tmpX * matrix[0, 0] + pp.Y * matrix[1, 0] + matrix[2, 0];
  254. pp.Y := tmpX * matrix[0, 1] + pp.Y * matrix[1, 1] + matrix[2, 1];
  255. inc(pp);
  256. end;
  257. end;
  258. end;
  259. //------------------------------------------------------------------------------
  260. procedure MatrixApply(const matrix: TMatrixD;
  261. img: TImage32; scaleAdjust: Boolean);
  262. begin
  263. AffineTransformImage(img, matrix, scaleAdjust);
  264. end;
  265. //------------------------------------------------------------------------------
  266. procedure MatrixApply(const matrix: TMatrixD;
  267. img, targetImg: TImage32; scaleAdjust: Boolean);
  268. begin
  269. AffineTransformImage(img, targetImg, matrix, scaleAdjust);
  270. end;
  271. //------------------------------------------------------------------------------
  272. procedure MatrixMultiply(var matrix1: TMatrixD; const matrix2: TMatrixD);
  273. var
  274. i, j: Integer;
  275. m: TMatrixD;
  276. begin
  277. for i := 0 to 2 do
  278. for j := 0 to 2 do
  279. m[i, j] :=
  280. (matrix1[i, 0] * matrix2[0, j]) +
  281. (matrix1[i, 1] * matrix2[1, j]) +
  282. (matrix1[i, 2] * matrix2[2, j]);
  283. matrix1 := m;
  284. end;
  285. //------------------------------------------------------------------------------
  286. procedure MatrixMultiply2(const matrix1: TMatrixD; var matrix2: TMatrixD);
  287. var
  288. i, j: Integer;
  289. m: TMatrixD;
  290. begin
  291. for i := 0 to 2 do
  292. for j := 0 to 2 do
  293. m[i, j] :=
  294. (matrix1[i, 0] * matrix2[0, j]) +
  295. (matrix1[i, 1] * matrix2[1, j]) +
  296. (matrix1[i, 2] * matrix2[2, j]);
  297. matrix2 := m;
  298. end;
  299. //------------------------------------------------------------------------------
  300. procedure MatrixScale(var matrix: TMatrixD; scaleX, scaleY: double);
  301. var
  302. m: TMatrixD;
  303. begin
  304. m := IdentityMatrix;
  305. if (scaleX = 0) or (scaleY = 0) then
  306. raise Exception(rsInvalidScale);
  307. if ValueAlmostOne(scaleX) and ValueAlmostOne(scaleY) then Exit;
  308. m[0, 0] := scaleX;
  309. m[1, 1] := scaleY;
  310. MatrixMultiply(matrix, m);
  311. end;
  312. //------------------------------------------------------------------------------
  313. procedure MatrixScale(var matrix: TMatrixD; scale: double);
  314. begin
  315. if (scale = 0) or (scale = 1) then Exit;
  316. MatrixScale(matrix, scale, scale);
  317. end;
  318. //------------------------------------------------------------------------------
  319. procedure MatrixRotate(var matrix: TMatrixD;
  320. const center: TPointD; angRad: double);
  321. var
  322. m: TMatrixD;
  323. sinA, cosA: double;
  324. begin
  325. if not PointsEqual(center, NullPointD) then
  326. begin
  327. NormalizeAngle(angRad);
  328. if angRad = 0 then Exit;
  329. {$IFNDEF CLOCKWISE_ROTATION_WITH_NEGATIVE_ANGLES}
  330. angRad := -angRad; //negated angle because of inverted Y-axis.
  331. {$ENDIF}
  332. m := IdentityMatrix;
  333. MatrixTranslate(matrix, -center.X, -center.Y);
  334. GetSinCos(angRad, sinA, cosA);
  335. m := IdentityMatrix;
  336. m[0, 0] := cosA; m[1, 0] := sinA;
  337. m[0, 1] := -sinA; m[1, 1] := cosA;
  338. MatrixMultiply(matrix, m);
  339. MatrixTranslate(matrix, center.X, center.Y);
  340. end
  341. else
  342. MatrixRotate(matrix, angRad)
  343. end;
  344. //------------------------------------------------------------------------------
  345. procedure MatrixRotate(var matrix: TMatrixD; angRad: double);
  346. var
  347. m: TMatrixD;
  348. sinA, cosA: double;
  349. begin
  350. NormalizeAngle(angRad);
  351. if angRad = 0 then Exit;
  352. {$IFNDEF CLOCKWISE_ROTATION_WITH_NEGATIVE_ANGLES}
  353. angRad := -angRad; //negated angle because of inverted Y-axis.
  354. {$ENDIF}
  355. m := IdentityMatrix;
  356. GetSinCos(angRad, sinA, cosA);
  357. m := IdentityMatrix;
  358. m[0, 0] := cosA; m[1, 0] := sinA;
  359. m[0, 1] := -sinA; m[1, 1] := cosA;
  360. MatrixMultiply(matrix, m);
  361. end;
  362. //------------------------------------------------------------------------------
  363. procedure MatrixTranslate(var matrix: TMatrixD; dx, dy: double);
  364. var
  365. m: TMatrixD;
  366. begin
  367. if ValueAlmostZero(dx) and ValueAlmostZero(dy) then Exit;
  368. m := IdentityMatrix;
  369. m[2, 0] := dx;
  370. m[2, 1] := dy;
  371. MatrixMultiply(matrix, m);
  372. end;
  373. //------------------------------------------------------------------------------
  374. procedure ScaleInternal(var matrix: TMatrixD; s: double);
  375. var
  376. i, j: Integer;
  377. begin
  378. for i := 0 to 2 do
  379. for j := 0 to 2 do
  380. matrix[i,j] := matrix[i,j] * s;
  381. end;
  382. //------------------------------------------------------------------------------
  383. function MatrixInvert(var matrix: TMatrixD): Boolean;
  384. var
  385. d: double;
  386. const
  387. tolerance = 1.0E-5;
  388. begin
  389. d := MatrixDeterminant(matrix);
  390. Result := abs(d) > tolerance;
  391. if Result then
  392. begin
  393. matrix := MatrixAdjugate(matrix);
  394. ScaleInternal(matrix, 1/d);
  395. end;
  396. end;
  397. //------------------------------------------------------------------------------
  398. procedure MatrixSkew(var matrix: TMatrixD; angleX, angleY: double);
  399. var
  400. m: TMatrixD;
  401. begin
  402. if ValueAlmostZero(angleX) and ValueAlmostZero(angleY) then Exit;
  403. m := IdentityMatrix;
  404. m[1, 0] := tan(angleX);
  405. m[0, 1] := tan(angleY);
  406. MatrixMultiply(matrix, m);
  407. end;
  408. //------------------------------------------------------------------------------
  409. procedure MatrixExtractScale(const mat: TMatrixD; out X, Y: double);
  410. begin
  411. // https://stackoverflow.com/a/32125700/359538
  412. X := Sqrt(Sqr(mat[0,0]) + Sqr(mat[0,1]));
  413. //Y := Sqrt(Sqr(mat[1,0]) + Sqr(mat[1,1]));
  414. Y := Abs((mat[0,0] * mat[1,1] - mat[1,0] * mat[0,1]) / X);
  415. end;
  416. //------------------------------------------------------------------------------
  417. procedure MatrixExtractScale(const mat: TMatrixD; out scale: double);
  418. var
  419. x,y: double;
  420. begin
  421. MatrixExtractScale(mat, x, y);
  422. scale := Average(x,y);
  423. end;
  424. //------------------------------------------------------------------------------
  425. procedure MatrixExtractTranslation(const mat: TMatrixD; out dx, dy: double);
  426. begin
  427. dx := mat[2,0];
  428. dy := mat[2,1];
  429. end;
  430. //------------------------------------------------------------------------------
  431. procedure MatrixExtractRotation(const mat: TMatrixD; out angle: double);
  432. begin
  433. angle := ArcTan2(mat[0,1], mat[0,0]);
  434. end;
  435. //------------------------------------------------------------------------------
  436. function MatrixExtractAll(const mat: TMatrixD;
  437. out angle: double; out scale, trans: TPointD): Boolean;
  438. var
  439. m00, m01, m10, m11: double;
  440. begin
  441. m00 := mat[0][0]; m10 := mat[1][0];
  442. m01 := mat[0][1]; m11 := mat[1][1];
  443. trans.X := mat[2][0];
  444. trans.Y := mat[2][1];
  445. angle := 0;
  446. scale := PointD(1,1);
  447. Result := (m00 <> 0) or (m01 <> 0);
  448. if not Result then Exit;
  449. angle := ArcTan2(m01, m00);
  450. // https://stackoverflow.com/a/32125700/359538
  451. scale.X := Sqrt(Sqr(mat[0,0]) + Sqr(mat[0,1]));
  452. scale.Y := (m00 * m11 - m10 * m01) / scale.X;
  453. end;
  454. //------------------------------------------------------------------------------
  455. {$IFDEF USE_DOWNSAMPLER_AUTOMATICALLY}
  456. function CanUseBoxDownsampler(const mat: TMatrixD; sx, sy: double): Boolean;
  457. begin
  458. // If the matrix looks like this after removing the scale,
  459. // the box downsampler can be used. (only translation and scaling)
  460. // cos(0) -sin(0) tx 1 0 tx
  461. // sin(0) cos(0) ty => 0 1 ty
  462. // 0 0 1 0 0 1
  463. {
  464. Result := (mat[0,0]/sx = 1) and (mat[0,1]/sx = 0) and
  465. (mat[1,0]/sy = 0) and (mat[1,1]/sy = 1) and
  466. (mat[2,0] = 0) and (mat[2,1] = 0) and
  467. (mat[2,2] = 1);
  468. }
  469. // We can skip the divisions, because m/s is only zero if m is zero
  470. // and m/s=1 is the same as m=s
  471. Result := (SameValue(mat[0,0], sx)) and (mat[0,1] = 0) and
  472. (mat[1,0] = 0) and (SameValue(mat[1,1], sy)) and
  473. (mat[2,0] = 0) and (mat[2,1] = 0) and
  474. (mat[2,2] = 1);
  475. end;
  476. {$ENDIF USE_DOWNSAMPLER_AUTOMATICALLY}
  477. //------------------------------------------------------------------------------
  478. // Affine Transformation
  479. //------------------------------------------------------------------------------
  480. function GetTransformBounds(img: TImage32; const matrix: TMatrixD): TRect;
  481. var
  482. pts: TPathD;
  483. begin
  484. pts := Rectangle(img.Bounds);
  485. MatrixApply(matrix, pts);
  486. Result := GetBounds(pts);
  487. end;
  488. //------------------------------------------------------------------------------
  489. function AffineTransformImage(img: TImage32; matrix: TMatrixD;
  490. scaleAdjust: Boolean): TPoint;
  491. begin
  492. Result := AffineTransformImage(img, img, matrix, scaleAdjust);
  493. end;
  494. //------------------------------------------------------------------------------
  495. function AffineTransformImage(img, targetImg: TImage32; matrix: TMatrixD;
  496. scaleAdjust: Boolean): TPoint;
  497. var
  498. i, j: integer;
  499. newWidth, newHeight: integer;
  500. sx, sy, x,y: double;
  501. xLimLo, yLimLo, xLimHi, yLimHi: double;
  502. pc: PColor32;
  503. tmp: TArrayOfColor32;
  504. dstRec: TRect;
  505. resampler: TResamplerFunction;
  506. {$IFDEF USE_DOWNSAMPLER_AUTOMATICALLY}
  507. useBoxDownsampler: Boolean;
  508. {$ENDIF}
  509. begin
  510. Result := NullPoint;
  511. if IsIdentityMatrix(matrix) or
  512. img.IsEmpty or (targetImg.Resampler = 0) then
  513. begin
  514. if targetImg <> img then targetImg.Assign(img);
  515. Exit;
  516. end;
  517. resampler := GetResampler(targetImg.Resampler);
  518. if not Assigned(resampler) then
  519. begin
  520. if targetImg <> img then targetImg.Assign(img);
  521. Exit;
  522. end;
  523. //auto-resize the image so it'll fit transformed image
  524. dstRec := img.Bounds;
  525. MatrixApply(matrix, dstRec);
  526. RectWidthHeight(dstRec, newWidth, newHeight);
  527. MatrixExtractScale(matrix, sx, sy);
  528. {$IFDEF USE_DOWNSAMPLER_AUTOMATICALLY}
  529. if (sx < 1.0) and (sy < 1.0) then
  530. begin
  531. //only use box downsampling when downsizing
  532. useBoxDownsampler := CanUseBoxDownsampler(matrix, sx, sy);
  533. end else
  534. useBoxDownsampler := false;
  535. if useBoxDownsampler then
  536. begin
  537. BoxDownSampling(img, targetImg, sx, sy);
  538. Exit;
  539. end;
  540. {$ENDIF}
  541. if scaleAdjust then
  542. begin
  543. sx := Max(1, sx * 0.5);
  544. sy := Max(1, sy * 0.5);
  545. end;
  546. //auto-translate the image too
  547. Result := dstRec.TopLeft;
  548. //starting with the result pixel coords, reverse lookup
  549. //the fractional coordinates in the untransformed image
  550. if not MatrixInvert(matrix) then
  551. begin
  552. if targetImg <> img then targetImg.Assign(img);
  553. Exit;
  554. end;
  555. NewColor32Array(tmp, newWidth * newHeight, True);
  556. pc := @tmp[0];
  557. xLimLo := -0.5/sx;
  558. xLimHi := img.Width + 0.5/sx;
  559. yLimLo := -0.5/sy;
  560. yLimHi := img.Height + 0.5/sy;
  561. for i := dstRec.Top to dstRec.Bottom -1 do
  562. begin
  563. for j := dstRec.Left to dstRec.Right -1 do
  564. begin
  565. x := j; y := i;
  566. MatrixApply(matrix, x, y);
  567. if (x <= xLimLo) or (x >= xLimHi) or (y <= yLimLo) or (y >= yLimHi) then
  568. pc^ := clNone32
  569. else
  570. // nb: -0.5 below is needed to properly center the transformed image
  571. // (and this is most obviously needed when there is large scaling)
  572. pc^ := resampler(img, x - 0.5, y - 0.5);
  573. inc(pc);
  574. end;
  575. end;
  576. targetImg.AssignPixelArray(tmp, newWidth, newHeight);
  577. end;
  578. //------------------------------------------------------------------------------
  579. // Projective Transformation
  580. //------------------------------------------------------------------------------
  581. procedure MatrixMulCoord(const matrix: TMatrixD; var x,y,z: double);
  582. {$IFDEF INLINE} inline; {$ENDIF}
  583. var
  584. xx, yy: double;
  585. begin
  586. xx := x; yy := y;
  587. x := matrix[0,0] *xx + matrix[0,1] *yy + matrix[0,2] *z;
  588. y := matrix[1,0] *xx + matrix[1,1] *yy + matrix[1,2] *z;
  589. z := matrix[2,0] *xx + matrix[2,1] *yy + matrix[2,2] *z;
  590. end;
  591. //------------------------------------------------------------------------------
  592. function BasisToPoints(x1, y1, x2, y2, x3, y3, x4, y4: double): TMatrixD;
  593. var
  594. m2: TMatrixD;
  595. z4: double;
  596. begin
  597. Result := Matrix(x1, x2, x3, y1, y2, y3, 1, 1, 1);
  598. m2 := MatrixAdjugate(Result);
  599. z4 := 1;
  600. MatrixMulCoord(m2, x4, y4, z4);
  601. m2 := Matrix(x4, 0, 0, 0, y4, 0, 0, 0, z4);
  602. MatrixMultiply(Result, m2);
  603. end;
  604. //------------------------------------------------------------------------------
  605. procedure GetSrcCoords256(const matrix: TMatrixD; var x, y: integer);
  606. {$IFDEF INLINE} inline; {$ENDIF}
  607. var
  608. xx,yy,zz: double;
  609. const
  610. Q: integer = MaxInt div 256;
  611. begin
  612. //returns coords multiplied by 256 in anticipation of the following
  613. //GetWeightedPixel function call which in turn expects the lower 8bits
  614. //of the integer coord value to represent a fraction.
  615. xx := x; yy := y; zz := 1;
  616. MatrixMulCoord(matrix, xx, yy, zz);
  617. if zz = 0 then
  618. begin
  619. if xx >= 0 then x := Q else x := -MaxInt;
  620. if yy >= 0 then y := Q else y := -MaxInt;
  621. end else
  622. begin
  623. xx := xx/zz;
  624. if xx > Q then x := MaxInt
  625. else if xx < -Q then x := -MaxInt
  626. else x := Round(xx *256);
  627. yy := yy/zz;
  628. if yy > Q then y := MaxInt
  629. else if yy < -Q then y := -MaxInt
  630. else y := Round(yy *256);
  631. end;
  632. end;
  633. //------------------------------------------------------------------------------
  634. procedure GetSrcCoords(const matrix: TMatrixD; var x, y: double);
  635. {$IFDEF INLINE} inline; {$ENDIF}
  636. var
  637. zz: double;
  638. const
  639. Q: integer = MaxInt div 256;
  640. begin
  641. //returns coords multiplied by 256 in anticipation of the following
  642. //GetWeightedPixel function call which in turn expects the lower 8bits
  643. //of the integer coord value to represent a fraction.
  644. zz := 1;
  645. MatrixMulCoord(matrix, x, y, zz);
  646. if zz = 0 then
  647. begin
  648. if x >= 0 then x := Q else x := -MaxDouble;
  649. if y >= 0 then y := Q else y := -MaxDouble;
  650. end else
  651. begin
  652. x := x/zz;
  653. if x > Q then x := MaxDouble
  654. else if x < -Q then x := -MaxDouble;
  655. y := y/zz;
  656. if y > Q then y := MaxDouble
  657. else if y < -Q then y := -MaxDouble
  658. end;
  659. end;
  660. //------------------------------------------------------------------------------
  661. function GetProjectionMatrix(const srcPts, dstPts: TPathD): TMatrixD;
  662. var
  663. dstMat: TMatrixD;
  664. begin
  665. if (length(srcPts) <> 4) or (length(dstPts) <> 4) then
  666. begin
  667. Result := IdentityMatrix;
  668. Exit;
  669. end;
  670. Result := BasisToPoints(srcPts[0].X, srcPts[0].Y,
  671. srcPts[1].X, srcPts[1].Y, srcPts[2].X, srcPts[2].Y, srcPts[3].X, srcPts[3].Y);
  672. dstMat := BasisToPoints(dstPts[0].X, dstPts[0].Y,
  673. dstPts[1].X, dstPts[1].Y, dstPts[2].X, dstPts[2].Y, dstPts[3].X, dstPts[3].Y);
  674. MatrixMultiply(Result, MatrixAdjugate(dstMat));
  675. end;
  676. //------------------------------------------------------------------------------
  677. function ProjectiveTransform(img: TImage32;
  678. const srcPts, dstPts: TPathD; const margins: TRect): Boolean;
  679. begin
  680. Result := ProjectiveTransform(img, img, srcPts, dstPts, margins);
  681. end;
  682. //------------------------------------------------------------------------------
  683. function ProjectiveTransform(img, targetImg: TImage32;
  684. const srcPts, dstPts: TPathD; const margins: TRect): Boolean;
  685. var
  686. w,h,i,j: integer;
  687. x,y: double;
  688. xLimLo, yLimLo, xLimHi, yLimHi: double;
  689. rec: TRect;
  690. dstPts2: TPathD;
  691. mat: TMatrixD;
  692. tmp: TArrayOfColor32;
  693. pc: PColor32;
  694. resampler: TResamplerFunction;
  695. begin
  696. //https://math.stackexchange.com/a/339033/384709
  697. if targetImg.Resampler = 0 then
  698. resampler := nil else
  699. resampler := GetResampler(targetImg.Resampler);
  700. Result := Assigned(resampler) and not img.IsEmpty and
  701. (Length(dstPts) = 4) and IsPathConvex(dstPts);
  702. if not Result then
  703. begin
  704. if targetImg <> img then targetImg.Assign(img);
  705. Exit;
  706. end;
  707. rec := GetBounds(dstPts);
  708. dec(rec.Left, margins.Left);
  709. dec(rec.Top, margins.Top);
  710. inc(rec.Right, margins.Right);
  711. inc(rec.Bottom, margins.Bottom);
  712. dstPts2 := TranslatePath(dstPts, -rec.Left, -rec.Top);
  713. xLimLo := -0.5;
  714. xLimHi := img.Width + 0.5;
  715. yLimLo := -0.5;
  716. yLimHi := img.Height + 0.5;
  717. mat := GetProjectionMatrix(srcPts, dstPts2);
  718. RectWidthHeight(rec, w, h);
  719. NewColor32Array(tmp, w * h, True);
  720. pc := @tmp[0];
  721. for i := 0 to h -1 do
  722. for j := 0 to w -1 do
  723. begin
  724. x := j; y := i;
  725. GetSrcCoords(mat, x, y);
  726. if (x <= xLimLo) or (x >= xLimHi) or (y <= yLimLo) or (y >= yLimHi) then
  727. pc^ := clNone32
  728. else
  729. pc^ := resampler(img, x -0.5, y -0.5);
  730. inc(pc);
  731. end;
  732. targetImg.BlockNotify;
  733. targetImg.AssignPixelArray(tmp, w, h);
  734. targetImg.UnblockNotify;
  735. end;
  736. //------------------------------------------------------------------------------
  737. // Spline transformations
  738. //------------------------------------------------------------------------------
  739. function ReColor(color, newColor: TColor32): TColor32;
  740. {$IFDEF INLINE} inline; {$ENDIF}
  741. begin
  742. Result := (color and $FF000000) or newColor;
  743. end;
  744. //------------------------------------------------------------------------------
  745. function InterpolateSegX(const pt1, pt2: TPointD): TPathD;
  746. var
  747. i, x1, x2: integer;
  748. xo,dydx: double;
  749. begin
  750. Result := nil;
  751. if pt2.X > pt1.X then
  752. begin
  753. x1 := Ceil(pt1.X);
  754. x2 := Ceil(pt2.X);
  755. if x1 = x2 then Exit;
  756. dydx := (pt2.Y - pt1.Y)/(pt2.X - pt1.X);
  757. xo := x1 -pt1.X;
  758. NewPointDArray(Result, x2-x1, True);
  759. for i:= 0 to x2 - x1 -1 do
  760. begin
  761. Result[i].X := x1 +i;
  762. Result[i].Y := pt1.Y + dydx * (xo +i);
  763. end;
  764. end else
  765. begin
  766. x1 := Floor(pt1.X);
  767. x2 := Floor(pt2.X);
  768. if x1 = x2 then Exit;
  769. dydx := (pt2.Y - pt1.Y)/(pt2.X - pt1.X);
  770. xo := x1 -pt1.X;
  771. NewPointDArray(Result, x1-x2, True);
  772. for i:= 0 to x1 - x2 -1 do
  773. begin
  774. Result[i].X := x1 -i;
  775. Result[i].Y := pt1.Y + dydx * (xo -i);
  776. end;
  777. end;
  778. end;
  779. //------------------------------------------------------------------------------
  780. function InterpolateSegY(const pt1, pt2: TPointD): TPathD;
  781. var
  782. i, y1,y2: integer;
  783. yo,dxdy: double;
  784. begin
  785. Result := nil;
  786. if pt2.Y > pt1.Y then
  787. begin
  788. y1 := Ceil(pt1.Y);
  789. y2 := Ceil(pt2.Y);
  790. if y1 = y2 then Exit;
  791. dxdy := (pt2.X - pt1.X)/(pt2.Y - pt1.Y);
  792. yo := y1 -pt1.Y;
  793. NewPointDArray(Result, y2-y1, True);
  794. for i:= 0 to y2 - y1 -1 do
  795. begin
  796. Result[i].Y := y1 +i;
  797. Result[i].X := pt1.X + dxdy * (yo +i);
  798. end;
  799. end else
  800. begin
  801. y1 := Floor(pt1.Y);
  802. y2 := Floor(pt2.Y);
  803. if y1 = y2 then Exit;
  804. dxdy := (pt2.X - pt1.X)/(pt2.Y - pt1.Y);
  805. yo := y1 -pt1.Y;
  806. NewPointDArray(Result, y1-y2, True);
  807. for i:= 0 to y1 - y2 -1 do
  808. begin
  809. Result[i].Y := y1 -i;
  810. Result[i].X := pt1.X + dxdy * (yo -i);
  811. end;
  812. end;
  813. end;
  814. //------------------------------------------------------------------------------
  815. function InterpolatePathForX(const path: TPathD): TPathD;
  816. var
  817. i,len: integer;
  818. tmp: TPathD;
  819. begin
  820. Result := nil;
  821. len := length(path);
  822. if len < 2 then Exit;
  823. for i := 1 to len -1 do
  824. begin
  825. tmp := InterpolateSegX(path[i-1], path[i]);
  826. ConcatPaths(Result, tmp);
  827. end;
  828. end;
  829. //------------------------------------------------------------------------------
  830. function InterpolatePathForY(const path: TPathD): TPathD;
  831. var
  832. i, len: integer;
  833. tmp: TPathD;
  834. begin
  835. Result := nil;
  836. len := length(path);
  837. if len < 2 then Exit;
  838. for i := 1 to len -1 do
  839. begin
  840. tmp := InterpolateSegY(path[i-1], path[i]);
  841. ConcatPaths(Result, tmp);
  842. end;
  843. end;
  844. //------------------------------------------------------------------------------
  845. function SplineVertTransform(img: TImage32; const topSpline: TPathD;
  846. splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean;
  847. begin
  848. Result := SplineVertTransform(img, img, topSpline, splineType, backColor, offset);
  849. end;
  850. //------------------------------------------------------------------------------
  851. function SplineVertTransform(img, targetImg: TImage32; const topSpline: TPathD;
  852. splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean;
  853. var
  854. i,j, w,h, len: integer;
  855. x,y, yy, q: double;
  856. yLimLo, yLimHi: double;
  857. distances: TArrayOfDouble;
  858. pc: PColor32;
  859. rec: TRect;
  860. tmp: TArrayOfColor32;
  861. topPath: TPathD;
  862. prevX: double;
  863. resampler: TResamplerFunction;
  864. backColoring, allowBackColoring: Boolean;
  865. begin
  866. offset := NullPoint;
  867. if targetImg.Resampler = 0 then
  868. resampler := nil else
  869. resampler := GetResampler(targetImg.Resampler);
  870. //convert the top spline control points into a flattened path
  871. if splineType = stQuadratic then
  872. topPath := FlattenQSpline(topSpline) else
  873. topPath := FlattenCSpline(topSpline);
  874. rec := GetBounds(topPath);
  875. //return false if the spline is invalid or there's no vertical transformation
  876. Result := Assigned(resampler) and not IsEmptyRect(rec);
  877. if not Result then
  878. begin
  879. if targetImg <> img then targetImg.Assign(img);
  880. Exit;
  881. end;
  882. offset := rec.TopLeft;
  883. topPath := InterpolatePathForX(topPath);
  884. len := Length(topPath);
  885. inc(rec.Bottom, img.Height);
  886. RectWidthHeight(rec, w, h);
  887. NewColor32Array(tmp, (w+1) * h, False);
  888. prevX := topPath[0].X;
  889. allowBackColoring := GetAlpha(backColor) > 2;
  890. backColor := backColor and $00FFFFFF;
  891. distances := GetCumulativeDistances(topPath);
  892. q := img.Width / distances[High(distances)];
  893. yLimLo := -0.5;
  894. yLimHi := img.Height + 0.5;
  895. for i := 0 to len -1 do
  896. begin
  897. pc := @tmp[Round(topPath[i].X)-rec.Left];
  898. backColoring := allowBackColoring and (prevX >= topPath[i].X);
  899. prevX := topPath[i].X;
  900. yy := topPath[i].Y;
  901. for j := rec.top to rec.bottom -1 do
  902. begin
  903. x := Distances[i]*q;
  904. y := j - yy;
  905. if (y < yLimLo) or (y > yLimHi) then
  906. // do nothing !
  907. else if backColoring then
  908. pc^ := BlendToAlpha(pc^, ReColor(resampler(img, x -0.5, y -0.5), backColor))
  909. else
  910. pc^ := BlendToAlpha(pc^, resampler(img, x -0.5, y -0.5));
  911. inc(pc, w);
  912. end;
  913. end;
  914. // tmp was creates with "(w+1)*h". We take advantage of the
  915. // memory manager's inplace shrink.
  916. SetLength(tmp, w * h);
  917. targetImg.AssignPixelArray(tmp, w, h);
  918. end;
  919. //------------------------------------------------------------------------------
  920. function SplineHorzTransform(img: TImage32; const leftSpline: TPathD;
  921. splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean;
  922. begin
  923. Result := SplineHorzTransform(img, img, leftSpline, splineType, backColor, offset);
  924. end;
  925. //------------------------------------------------------------------------------
  926. function SplineHorzTransform(img, targetImg: TImage32; const leftSpline: TPathD;
  927. splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean;
  928. var
  929. i,j, len, w,h: integer;
  930. x,y, xx, q, prevY: double;
  931. xLimLo, xLimHi: double;
  932. leftPath: TPathD;
  933. distances: TArrayOfDouble;
  934. rec: TRect;
  935. pc: PColor32;
  936. tmp: TArrayOfColor32;
  937. backColoring, allowBackColoring: Boolean;
  938. resampler: TResamplerFunction;
  939. begin
  940. offset := NullPoint;
  941. if targetImg.Resampler = 0 then
  942. resampler := nil else
  943. resampler := GetResampler(targetImg.Resampler);
  944. //convert the left spline control points into a flattened path
  945. if splineType = stQuadratic then
  946. leftPath := FlattenQSpline(leftSpline) else
  947. leftPath := FlattenCSpline(leftSpline);
  948. rec := GetBounds(leftPath);
  949. //return false if the spline is invalid or there's no horizontal transformation
  950. Result := Assigned(resampler) and not IsEmptyRect(rec);
  951. if not Result then
  952. begin
  953. if targetImg <> img then targetImg.Assign(img);
  954. Exit;
  955. end;
  956. offset := rec.TopLeft;
  957. leftPath := InterpolatePathForY(leftPath);
  958. len := Length(leftPath);
  959. inc(rec.Right, img.Width);
  960. RectWidthHeight(rec, w, h);
  961. NewColor32Array(tmp, w * (h+1), False);
  962. prevY := leftPath[0].Y;
  963. allowBackColoring := GetAlpha(backColor) > 2;
  964. backColor := backColor and $00FFFFFF;
  965. distances := GetCumulativeDistances(leftPath);
  966. q := img.Height / distances[High(distances)];;
  967. xLimLo := -0.5;
  968. xLimHi := img.Width + 0.5;
  969. for i := 0 to len -1 do
  970. begin
  971. pc := @tmp[Round(leftPath[i].Y - rec.Top) * w];
  972. backColoring := allowBackColoring and (prevY >= leftPath[i].Y);
  973. prevY := leftPath[i].Y;
  974. xx := leftPath[i].X;
  975. y := Distances[i]*q;
  976. for j := rec.left to rec.right -1 do
  977. begin
  978. x := j - xx;
  979. if (x < xLimLo) or (x > xLimHi) then
  980. // do nothing !
  981. else if backColoring then
  982. pc^ := BlendToAlpha(pc^, ReColor(resampler(img, x -0.5, y -0.5), backColor))
  983. else
  984. pc^ := BlendToAlpha(pc^, resampler(img, x -0.5, y -0.5));
  985. inc(pc);
  986. end;
  987. end;
  988. // tmp was creates with "w*(h+1)". We take advantage of the
  989. // memory manager's inplace shrink.
  990. SetLength(tmp, w * h);
  991. targetImg.AssignPixelArray(tmp, w, h);
  992. end;
  993. //------------------------------------------------------------------------------
  994. // Miscellaneous WeightedColor function
  995. //------------------------------------------------------------------------------
  996. function LimitByte(val: Cardinal): byte; {$IFDEF INLINE} inline; {$ENDIF}
  997. begin
  998. if val > 255 then result := 255
  999. else result := val;
  1000. end;
  1001. //------------------------------------------------------------------------------
  1002. // TWeightedColor
  1003. //------------------------------------------------------------------------------
  1004. procedure TWeightedColor.Reset;
  1005. {$IFDEF CPUX64}
  1006. var
  1007. Zero: Int64;
  1008. {$ENDIF CPUX64}
  1009. begin
  1010. {$IFDEF CPUX64}
  1011. Zero := 0;
  1012. fAddCount := Zero;
  1013. fAlphaTot := Zero;
  1014. fColorTotR := Zero;
  1015. fColorTotG := Zero;
  1016. fColorTotB := Zero;
  1017. {$ELSE}
  1018. fAddCount := 0;
  1019. fAlphaTot := 0;
  1020. fColorTotR := 0;
  1021. fColorTotG := 0;
  1022. fColorTotB := 0;
  1023. {$ENDIF CPUX64}
  1024. end;
  1025. //------------------------------------------------------------------------------
  1026. procedure TWeightedColor.Reset(c: TColor32; w: Integer);
  1027. var
  1028. a: Cardinal;
  1029. begin
  1030. fAddCount := w;
  1031. a := w * Byte(c shr 24);
  1032. if a = 0 then
  1033. begin
  1034. fAlphaTot := 0;
  1035. fColorTotB := 0;
  1036. fColorTotG := 0;
  1037. fColorTotR := 0;
  1038. end else
  1039. begin
  1040. fAlphaTot := a;
  1041. fColorTotB := (a * Byte(c));
  1042. fColorTotG := (a * Byte(c shr 8));
  1043. fColorTotR := (a * Byte(c shr 16));
  1044. end;
  1045. end;
  1046. //------------------------------------------------------------------------------
  1047. procedure TWeightedColor.AddWeight(w: Integer);
  1048. begin
  1049. inc(fAddCount, w);
  1050. end;
  1051. //------------------------------------------------------------------------------
  1052. procedure TWeightedColor.Add(c: TColor32; w: Integer);
  1053. var
  1054. a: Int64;
  1055. begin
  1056. inc(fAddCount, w);
  1057. a := Byte(c shr 24);
  1058. if a <> 0 then
  1059. begin
  1060. a := a * Cardinal(w);
  1061. inc(fAlphaTot, a);
  1062. inc(fColorTotB, (a * Byte(c)));
  1063. inc(fColorTotG, (a * Byte(c shr 8)));
  1064. inc(fColorTotR, (a * Byte(c shr 16)));
  1065. end;
  1066. end;
  1067. //------------------------------------------------------------------------------
  1068. procedure TWeightedColor.Add(c: TColor32);
  1069. // Optimized for w=1
  1070. var
  1071. a: Int64;
  1072. begin
  1073. inc(fAddCount);
  1074. a := Byte(c shr 24);
  1075. if a = 0 then Exit;
  1076. inc(fAlphaTot, a);
  1077. inc(fColorTotB, (a * Byte(c)));
  1078. inc(fColorTotG, (a * Byte(c shr 8)));
  1079. inc(fColorTotR, (a * Byte(c shr 16)));
  1080. end;
  1081. //------------------------------------------------------------------------------
  1082. procedure TWeightedColor.Add(const other: TWeightedColor);
  1083. begin
  1084. inc(fAddCount, other.fAddCount);
  1085. inc(fAlphaTot, other.fAlphaTot);
  1086. inc(fColorTotR, other.fColorTotR);
  1087. inc(fColorTotG, other.fColorTotG);
  1088. inc(fColorTotB, other.fColorTotB);
  1089. end;
  1090. //------------------------------------------------------------------------------
  1091. procedure TWeightedColor.Subtract(c: TColor32; w: Integer);
  1092. var
  1093. a: Int64;
  1094. begin
  1095. dec(fAddCount, w);
  1096. a := w * Byte(c shr 24);
  1097. if a = 0 then Exit;
  1098. dec(fAlphaTot, a);
  1099. dec(fColorTotB, (a * Byte(c)));
  1100. dec(fColorTotG, (a * Byte(c shr 8)));
  1101. dec(fColorTotR, (a * Byte(c shr 16)));
  1102. end;
  1103. //------------------------------------------------------------------------------
  1104. procedure TWeightedColor.Subtract(c: TColor32);
  1105. // Optimized for w=1
  1106. var
  1107. a: Int64;
  1108. begin
  1109. dec(fAddCount);
  1110. a := Byte(c shr 24);
  1111. if a = 0 then Exit;
  1112. dec(fAlphaTot, a);
  1113. dec(fColorTotB, (a * Byte(c)));
  1114. dec(fColorTotG, (a * Byte(c shr 8)));
  1115. dec(fColorTotR, (a * Byte(c shr 16)));
  1116. end;
  1117. //------------------------------------------------------------------------------
  1118. procedure TWeightedColor.Subtract(const other: TWeightedColor);
  1119. begin
  1120. dec(fAddCount, other.fAddCount);
  1121. dec(fAlphaTot, other.fAlphaTot);
  1122. dec(fColorTotR, other.fColorTotR);
  1123. dec(fColorTotG, other.fColorTotG);
  1124. dec(fColorTotB, other.fColorTotB);
  1125. end;
  1126. //------------------------------------------------------------------------------
  1127. function TWeightedColor.AddSubtract(addC, subC: TColor32): Boolean;
  1128. var
  1129. a: Int64;
  1130. begin
  1131. // add+subtract => fAddCount stays the same
  1132. // skip identical colors
  1133. Result := False;
  1134. if addC = subC then Exit;
  1135. a := Byte(addC shr 24);
  1136. if a > 0 then
  1137. begin
  1138. inc(fAlphaTot, a);
  1139. inc(fColorTotB, (a * Byte(addC)));
  1140. inc(fColorTotG, (a * Byte(addC shr 8)));
  1141. inc(fColorTotR, (a * Byte(addC shr 16)));
  1142. Result := True;
  1143. end;
  1144. a := Byte(subC shr 24);
  1145. if a > 0 then
  1146. begin
  1147. dec(fAlphaTot, a);
  1148. dec(fColorTotB, (a * Byte(subC)));
  1149. dec(fColorTotG, (a * Byte(subC shr 8)));
  1150. dec(fColorTotR, (a * Byte(subC shr 16)));
  1151. Result := True;
  1152. end;
  1153. end;
  1154. //------------------------------------------------------------------------------
  1155. function TWeightedColor.AddNoneSubtract(c: TColor32): Boolean;
  1156. var
  1157. a: Int64;
  1158. begin
  1159. // add+subtract => fAddCount stays the same
  1160. a := Byte(c shr 24);
  1161. if a > 0 then
  1162. begin
  1163. dec(fAlphaTot, a);
  1164. dec(fColorTotB, (a * Byte(c)));
  1165. dec(fColorTotG, (a * Byte(c shr 8)));
  1166. dec(fColorTotR, (a * Byte(c shr 16)));
  1167. Result := True;
  1168. end
  1169. else
  1170. Result := False;
  1171. end;
  1172. //------------------------------------------------------------------------------
  1173. function TWeightedColor.GetColor: TColor32;
  1174. var
  1175. oneDivAlphaTot: double;
  1176. alpha: Integer;
  1177. begin
  1178. result := clNone32;
  1179. if (fAlphaTot <= 0) or (fAddCount <= 0) then
  1180. Exit;
  1181. {$IFDEF CPUX86}
  1182. if fAlphaTot and $FFFFFFFF80000000 = 0 then // small, so can avoid _lldiv call
  1183. alpha := (Cardinal(fAlphaTot) + (Cardinal(fAddCount) shr 1)) div
  1184. Cardinal(fAddCount)
  1185. else
  1186. {$ENDIF CPUX86}
  1187. alpha := (fAlphaTot + (Cardinal(fAddCount) shr 1)) div Cardinal(fAddCount);
  1188. result := TColor32(Min(255, alpha)) shl 24;
  1189. // alpha weighting has been applied to color channels, so div by fAlphaTot
  1190. if fAlphaTot < DivOneByXTableSize then // use precalculated 1/X values
  1191. oneDivAlphaTot := DivOneByXTable[fAlphaTot] else
  1192. oneDivAlphaTot := 1/(fAlphaTot);
  1193. // 1. Skip zero calculations.
  1194. // 2. LimitByte(Integer): Values can't be less than 0, so don't use ClampByte.
  1195. // 3. x86: Round expects the value in the st(0)/xmm1 FPU register.
  1196. // Thus we need to do the calculation and Round call in one expression.
  1197. // Otherwise the compiler will use a temporary double variable on
  1198. // the stack that will cause unnecessary store and load operations.
  1199. if fColorTotB > 0 then
  1200. result := result or LimitByte(System.Round(fColorTotB * oneDivAlphaTot));
  1201. if fColorTotG > 0 then
  1202. result := result or LimitByte(System.Round(fColorTotG * oneDivAlphaTot)) shl 8;
  1203. if fColorTotR > 0 then
  1204. result := result or LimitByte(System.Round(fColorTotR * oneDivAlphaTot)) shl 16;
  1205. end;
  1206. //------------------------------------------------------------------------------
  1207. // Initialization
  1208. //------------------------------------------------------------------------------
  1209. procedure MakeDivOneByXTable;
  1210. var
  1211. i: Integer;
  1212. begin
  1213. DivOneByXTable[0] := 0; // NaN
  1214. for i := 1 to High(DivOneByXTable) do
  1215. DivOneByXTable[i] := 1/i;
  1216. end;
  1217. initialization
  1218. MakeDivOneByXTable;
  1219. end.