GR32_Blurs.pas 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056
  1. unit GR32_Blurs;
  2. (* BEGIN LICENSE BLOCK *********************************************************
  3. * Version: MPL 1.1 *
  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 GR32_Blurs. The Gaussian blur algorithm was inspired *
  23. * by code published by Mario Klingemann and has been used with his permission. *
  24. * See also http://incubator.quasimondo.com *
  25. * *
  26. * Copyright 2012 - Angus Johnson *
  27. * *
  28. * Version 5.0 (Last updated 25-Sep-2012) *
  29. * *
  30. * END LICENSE BLOCK ***********************************************************)
  31. interface
  32. {$include GR32.inc}
  33. {$message 'The functions in the GR32_Blurs unit are being deprecated in favor of the GR32.Blur unit'}
  34. uses
  35. {$IFDEF FPC}
  36. LCLIntf,
  37. {$ELSE}
  38. Windows, Types,
  39. {$ENDIF}
  40. SysUtils, Classes, Math, GR32, GR32.Blur;
  41. type
  42. TBlurFunction = procedure(Bitmap32: TBitmap32; Radius: TFloat);
  43. TBlurFunctionBounds = procedure(Bitmap32: TBitmap32; Radius: TFloat; const Bounds: TRect);
  44. TBlurFunctionRegion = procedure(Bitmap32: TBitmap32; Radius: TFloat; const BlurRegion: TArrayOfFloatPoint);
  45. (*
  46. GaussianBlur appears to be based on Mario Klingemann's "stackblur" algorithm which
  47. in turn is a "reinvention" of a simple sliding-accumulator box blur. It performs what
  48. corresponds to a two pass box blur (i.e. a triangle blur).
  49. https://web.archive.org/web/20200811093037/http://incubator.quasimondo.com/processing/fast_blur_deluxe.php
  50. https://underdestruction.com/2004/02/25/stackblur-2004/
  51. *)
  52. procedure GaussianBlur(Bitmap32: TBitmap32; Radius: TFloat); overload; deprecated 'Use Blur32 in GR32.Blur instead';
  53. procedure GaussianBlur(Bitmap32: TBitmap32; Radius: TFloat; const Bounds: TRect); overload; deprecated 'Use Blur32 in GR32.Blur instead';
  54. procedure GaussianBlur(Bitmap32: TBitmap32; Radius: TFloat; const BlurRegion: TArrayOfFloatPoint); overload; deprecated 'Use Blur32 in GR32.Blur instead';
  55. procedure GaussianBlurGamma(Bitmap32: TBitmap32; Radius: TFloat); overload; deprecated 'Use GammaBlur32 in GR32.Blur instead';
  56. procedure GaussianBlurGamma(Bitmap32: TBitmap32; Radius: TFloat; const Bounds: TRect); overload; deprecated 'Use Blur32 in GR32.Blur instead';
  57. procedure GaussianBlurGamma(Bitmap32: TBitmap32; Radius: TFloat; const BlurRegion: TArrayOfFloatPoint); overload; deprecated 'Use Blur32 in GR32.Blur instead';
  58. (*
  59. FastBlur: Three pass box blur
  60. *)
  61. procedure FastBlur(Bitmap32: TBitmap32; Radius: TFloat); overload; deprecated 'Use Blur32 in GR32.Blur instead';
  62. procedure FastBlur(Bitmap32: TBitmap32; Radius: TFloat; const Bounds: TRect); overload; deprecated 'Use Blur32 in GR32.Blur instead';
  63. procedure FastBlur(Bitmap32: TBitmap32; Radius: TFloat; const BlurRegion: TArrayOfFloatPoint); overload; deprecated 'Use Blur32 in GR32.Blur instead';
  64. procedure FastBlurGamma(Bitmap32: TBitmap32; Radius: TFloat); overload; deprecated 'Use GammaBlur32 in GR32.Blur instead';
  65. procedure FastBlurGamma(Bitmap32: TBitmap32; Radius: TFloat; const Bounds: TRect); overload; deprecated 'Use GammaBlur32 in GR32.Blur instead';
  66. procedure FastBlurGamma(Bitmap32: TBitmap32; Radius: TFloat; const BlurRegion: TArrayOfFloatPoint); overload; deprecated 'Use GammaBlur32 in GR32.Blur instead';
  67. (*
  68. MotionBlur: One-dimensional blur with rotation (rotate, blur horizontal, rotate back)
  69. *)
  70. procedure MotionBlur(Bitmap32: TBitmap32; Dist, AngleDeg: TFloat; Bidirectional: Boolean = True); overload;
  71. procedure MotionBlur(Bitmap32: TBitmap32; Dist, AngleDeg: TFloat; const Bounds: TRect; Bidirectional: Boolean = True); overload;
  72. procedure MotionBlur(Bitmap32: TBitmap32; Dist, AngleDeg: TFloat; const BlurRegion: TArrayOfFloatPoint; Bidirectional: Boolean = True); overload;
  73. procedure MotionBlurGamma(Bitmap32: TBitmap32; Dist, AngleDeg: TFloat; Bidirectional: Boolean = True); overload;
  74. procedure MotionBlurGamma(Bitmap32: TBitmap32; Dist, AngleDeg: TFloat; const Bounds: TRect; Bidirectional: Boolean = True); overload;
  75. procedure MotionBlurGamma(Bitmap32: TBitmap32; Dist, AngleDeg: TFloat; const BlurRegion: TArrayOfFloatPoint; Bidirectional: Boolean = True); overload;
  76. const
  77. GaussianBlurSimple: array [Boolean] of TBlurFunction = (Blur32, GammaBlur32) deprecated 'This const will be removed. Make a local copy of it instead';
  78. GaussianBlurBounds: array [Boolean] of TBlurFunctionBounds = (Blur32, GammaBlur32) deprecated 'This const will be removed. Make a local copy of it instead';
  79. GaussianBlurRegion: array [Boolean] of TBlurFunctionRegion = (Blur32, GammaBlur32) deprecated 'This const will be removed. Make a local copy of it instead';
  80. FastBlurSimple: array [Boolean] of TBlurFunction = (Blur32, GammaBlur32) deprecated 'This const will be removed. Make a local copy of it instead';
  81. FastBlurBounds: array [Boolean] of TBlurFunctionBounds = (Blur32, GammaBlur32) deprecated 'This const will be removed. Make a local copy of it instead';
  82. FastBlurRegion: array [Boolean] of TBlurFunctionRegion = (Blur32, GammaBlur32) deprecated 'This const will be removed. Make a local copy of it instead';
  83. implementation
  84. uses
  85. GR32_Blend, GR32_Gamma, GR32_Resamplers, GR32_Polygons, GR32_LowLevel,
  86. GR32_VectorUtils, GR32_Transforms;
  87. type
  88. TSumRecInt64 = record
  89. B, G, R, A: Int64;
  90. Sum: Integer;
  91. end;
  92. TSumRecord = record
  93. B, G, R, A: Integer;
  94. Sum: Integer;
  95. end;
  96. const
  97. ChannelSize = 256; // ie 1 byte for each of A,R,G & B in TColor32
  98. ChannelSizeMin1 = ChannelSize - 1;
  99. procedure ResetSumRecord(var SumRecord: TSumRecInt64); overload;
  100. {$IFDEF USEINLINING} inline; {$ENDIF}
  101. begin
  102. FillChar(SumRecord, SizeOf(SumRecord), 0);
  103. end;
  104. procedure ResetSumRecord(var SumRecord: TSumRecord); overload;
  105. {$IFDEF USEINLINING} inline; {$ENDIF}
  106. begin
  107. FillChar(SumRecord, SizeOf(SumRecord), 0);
  108. end;
  109. function Divide(SumRecord: TSumRecInt64): TSumRecInt64; overload;
  110. {$IFDEF USEINLINING} inline; {$ENDIF}
  111. begin
  112. Result.A := SumRecord.A div SumRecord.Sum;
  113. Result.R := SumRecord.R div SumRecord.Sum;
  114. Result.G := SumRecord.G div SumRecord.Sum;
  115. Result.B := SumRecord.B div SumRecord.Sum;
  116. end;
  117. function Divide(SumRecord: TSumRecord): TSumRecord; overload;
  118. {$IFDEF USEINLINING} inline; {$ENDIF}
  119. begin
  120. Result.A := SumRecord.A div SumRecord.Sum;
  121. Result.R := SumRecord.R div SumRecord.Sum;
  122. Result.G := SumRecord.G div SumRecord.Sum;
  123. Result.B := SumRecord.B div SumRecord.Sum;
  124. end;
  125. function DivideToColor32(SumRecord: TSumRecInt64): TColor32Entry; overload;
  126. {$IFDEF USEINLINING} inline; {$ENDIF}
  127. begin
  128. Result.A := SumRecord.A div SumRecord.Sum;
  129. Result.R := SumRecord.R div SumRecord.Sum;
  130. Result.G := SumRecord.G div SumRecord.Sum;
  131. Result.B := SumRecord.B div SumRecord.Sum;
  132. end;
  133. function DivideToColor32(SumRecord: TSumRecord): TColor32Entry; overload;
  134. {$IFDEF USEINLINING} inline; {$ENDIF}
  135. begin
  136. Result.A := SumRecord.A div SumRecord.Sum;
  137. Result.R := SumRecord.R div SumRecord.Sum;
  138. Result.G := SumRecord.G div SumRecord.Sum;
  139. Result.B := SumRecord.B div SumRecord.Sum;
  140. end;
  141. { GaussianBlur }
  142. {$R-}
  143. procedure GaussianBlur(Bitmap32: TBitmap32; Radius: TFloat);
  144. begin
  145. GaussianBlur(Bitmap32, Radius, Bitmap32.BoundsRect);
  146. end;
  147. procedure GaussianBlur(Bitmap32: TBitmap32; Radius: TFloat; const Bounds: TRect);
  148. var
  149. Q, I, J, X, Y, ImageWidth, RowOffset, RadiusI: Integer;
  150. RecLeft, RecTop, RecRight, RecBottom: Integer;
  151. ImagePixels: PColor32EntryArray;
  152. RadiusSq, RadiusRevSq, KernelSize: Integer;
  153. SumRec: TSumRecInt64;
  154. PreMulArray: array of TColor32Entry;
  155. SumArray: array of TSumRecInt64;
  156. GaussLUT: array of array of Cardinal;
  157. begin
  158. RadiusI := Round(Radius);
  159. if RadiusI < 1 then
  160. Exit
  161. else if RadiusI > 128 then
  162. RadiusI := 128; // nb: performance degrades exponentially with >> Radius
  163. // initialize the look-up-table ...
  164. KernelSize := RadiusI * 2 + 1;
  165. SetLength(GaussLUT, KernelSize);
  166. for I := 0 to KernelSize - 1 do
  167. SetLength(GaussLUT[I], ChannelSize);
  168. for I := 1 to RadiusI do
  169. begin
  170. RadiusRevSq := Round((Radius + 1 - I) * (Radius + 1 - I));
  171. for J := 0 to ChannelSizeMin1 do
  172. begin
  173. GaussLUT[RadiusI - I][J] := RadiusRevSq * J;
  174. GaussLUT[RadiusI + I][J] := GaussLUT[RadiusI - I][J];
  175. end;
  176. end;
  177. RadiusSq := Round((Radius + 1) * (Radius + 1));
  178. for J := 0 to ChannelSizeMin1 do
  179. GaussLUT[RadiusI][J] := RadiusSq * J;
  180. ImageWidth := Bitmap32.Width;
  181. SetLength(SumArray, ImageWidth * Bitmap32.Height);
  182. ImagePixels := PColor32EntryArray(Bitmap32.Bits);
  183. RecLeft := Max(Bounds.Left, 0);
  184. RecTop := Max(Bounds.Top, 0);
  185. RecRight := Min(Bounds.Right, ImageWidth - 1);
  186. RecBottom := Min(Bounds.Bottom, Bitmap32.Height - 1);
  187. RowOffset := RecTop * ImageWidth;
  188. SetLength(PreMulArray, Bitmap32.Width);
  189. for Y := RecTop to RecBottom do
  190. begin
  191. // initialize PreMulArray for the row ...
  192. Q := (Y * ImageWidth) + RecLeft;
  193. for X := RecLeft to RecRight do
  194. with ImagePixels[Q] do
  195. begin
  196. PreMulArray[X].A := A;
  197. PreMulArray[X].R := MulDiv255Table[R, A];
  198. PreMulArray[X].G := MulDiv255Table[G, A];
  199. PreMulArray[X].B := MulDiv255Table[B, A];
  200. Inc(Q);
  201. end;
  202. for X := RecLeft to RecRight do
  203. begin
  204. ResetSumRecord(SumRec);
  205. I := Max(X - RadiusI, RecLeft);
  206. Q := I - (X - RadiusI);
  207. for I := I to Min(X + RadiusI, RecRight) do
  208. with PreMulArray[I] do
  209. begin
  210. Inc(SumRec.A, GaussLUT[Q][A]);
  211. Inc(SumRec.R, GaussLUT[Q][R]);
  212. Inc(SumRec.G, GaussLUT[Q][G]);
  213. Inc(SumRec.B, GaussLUT[Q][B]);
  214. Inc(SumRec.Sum, GaussLUT[Q][1]);
  215. Inc(Q);
  216. end;
  217. Q := RowOffset + X;
  218. SumArray[Q] := Divide(SumRec);
  219. end;
  220. Inc(RowOffset, ImageWidth);
  221. end;
  222. RowOffset := RecTop * ImageWidth;
  223. for Y := RecTop to RecBottom do
  224. begin
  225. for X := RecLeft to RecRight do
  226. begin
  227. ResetSumRecord(SumRec);
  228. I := Max(Y - RadiusI, RecTop);
  229. Q := I - (Y - RadiusI);
  230. for I := I to Min(Y + RadiusI, RecBottom) do
  231. with SumArray[X + I * ImageWidth] do
  232. begin
  233. Inc(SumRec.A, GaussLUT[Q][A]);
  234. Inc(SumRec.R, GaussLUT[Q][R]);
  235. Inc(SumRec.G, GaussLUT[Q][G]);
  236. Inc(SumRec.B, GaussLUT[Q][B]);
  237. Inc(SumRec.Sum, GaussLUT[Q][1]);
  238. Inc(Q);
  239. end;
  240. with ImagePixels[RowOffset + X] do
  241. begin
  242. A := (SumRec.A div SumRec.Sum);
  243. R := DivMul255Table[A, (SumRec.R div SumRec.Sum)];
  244. G := DivMul255Table[A, (SumRec.G div SumRec.Sum)];
  245. B := DivMul255Table[A, (SumRec.B div SumRec.Sum)];
  246. end;
  247. end;
  248. Inc(RowOffset, ImageWidth);
  249. end;
  250. end;
  251. procedure GaussianBlur(Bitmap32: TBitmap32; Radius: TFloat;
  252. const BlurRegion: TArrayOfFloatPoint);
  253. var
  254. Q, I, J, X, Y, ImageWidth, RowOffset, RadiusI: Integer;
  255. RecLeft, RecTop, RecRight, RecBottom: Integer;
  256. ImagePixels: PColor32EntryArray;
  257. RadiusSq, RadiusRevSq, KernelSize: Integer;
  258. SumRec: TSumRecInt64;
  259. SumArray: array of TSumRecInt64;
  260. GaussLUT: array of array of Cardinal;
  261. PreMulArray: array of TColor32Entry;
  262. Mask: TBitmap32;
  263. Clr, MaskClr: TColor32Entry;
  264. Pts: TArrayOfFloatPoint;
  265. Bounds: TRect;
  266. begin
  267. with PolygonBounds(BlurRegion) do
  268. Bounds := Rect(Floor(Left), Floor(Top), Ceil(Right), Ceil(Bottom));
  269. if Bounds.Left < 0 then Bounds.Left := 0;
  270. if Bounds.Top < 0 then Bounds.Top := 0;
  271. if Bounds.Right >= Bitmap32.Width then Bounds.Right := Bitmap32.Width - 1;
  272. if Bounds.Bottom >= Bitmap32.Height then Bounds.Bottom := Bitmap32.Height - 1;
  273. RadiusI := round(Radius);
  274. if (RadiusI < 1) or (Bounds.Right <= Bounds.Left) or (Bounds.Bottom <= Bounds.Top) then
  275. Exit
  276. else if RadiusI > 128 then
  277. RadiusI := 128; // nb: performance degrades exponentially with >> Radius
  278. Mask := TBitmap32.Create;
  279. try
  280. Mask.SetSize(Bounds.Right - Bounds.Left + 1, Bounds.Bottom - Bounds.Top + 1);
  281. SetLength(Pts, Length(BlurRegion));
  282. for I := 0 to High(BlurRegion) do
  283. begin
  284. Pts[I].X := BlurRegion[I].X - Bounds.Left;
  285. Pts[I].Y := BlurRegion[I].Y - Bounds.Top;
  286. end;
  287. PolygonFS(Mask, Pts, clWhite32);
  288. // initialize the look-up-table ...
  289. KernelSize := RadiusI * 2 + 1;
  290. SetLength(GaussLUT, KernelSize);
  291. for I := 0 to KernelSize - 1 do
  292. SetLength(GaussLUT[I], ChannelSize);
  293. for I := 1 to RadiusI do
  294. begin
  295. RadiusRevSq := Round((Radius + 1 - I) * (Radius + 1 - I));
  296. for J := 0 to ChannelSizeMin1 do
  297. begin
  298. GaussLUT[RadiusI - I][J] := RadiusRevSq * J;
  299. GaussLUT[RadiusI + I][J] := GaussLUT[RadiusI - I][J];
  300. end;
  301. end;
  302. RadiusSq := Round((Radius + 1) * (Radius + 1));
  303. for J := 0 to ChannelSizeMin1 do
  304. GaussLUT[RadiusI][J] := RadiusSq * J;
  305. ImageWidth := Bitmap32.Width;
  306. SetLength(SumArray, ImageWidth * Bitmap32.Height);
  307. ImagePixels := PColor32EntryArray(Bitmap32.Bits);
  308. RecLeft := Max(Bounds.Left, 0);
  309. RecTop := Max(Bounds.Top, 0);
  310. RecRight := Min(Bounds.Right, ImageWidth - 1);
  311. RecBottom := Min(Bounds.Bottom, Bitmap32.Height - 1);
  312. RowOffset := RecTop * ImageWidth;
  313. SetLength(PreMulArray, Bitmap32.Width);
  314. for Y := RecTop to RecBottom do
  315. begin
  316. // initialize PreMulArray for the current row ...
  317. Q := (Y * ImageWidth) + RecLeft;
  318. for X := RecLeft to RecRight do
  319. with ImagePixels[Q] do
  320. begin
  321. PreMulArray[X].A := A;
  322. PreMulArray[X].R := MulDiv255Table[R, A];
  323. PreMulArray[X].G := MulDiv255Table[G, A];
  324. PreMulArray[X].B := MulDiv255Table[B, A];
  325. Inc(Q);
  326. end;
  327. for X := RecLeft to RecRight do
  328. begin
  329. ResetSumRecord(SumRec);
  330. I := Max(X - RadiusI, RecLeft);
  331. Q := I - (X - RadiusI);
  332. for I := I to Min(X + RadiusI, RecRight) do
  333. with PreMulArray[I] do
  334. begin
  335. Inc(SumRec.A, GaussLUT[Q][A]);
  336. Inc(SumRec.R, GaussLUT[Q][R]);
  337. Inc(SumRec.G, GaussLUT[Q][G]);
  338. Inc(SumRec.B, GaussLUT[Q][B]);
  339. Inc(SumRec.Sum, GaussLUT[Q][1]);
  340. Inc(Q);
  341. end;
  342. Q := RowOffset + X;
  343. SumArray[Q] := Divide(SumRec);
  344. end;
  345. Inc(RowOffset, ImageWidth);
  346. end;
  347. RowOffset := RecTop * ImageWidth;
  348. for Y := RecTop to RecBottom do
  349. begin
  350. for X := RecLeft to RecRight do
  351. begin
  352. MaskClr.ARGB := Mask.Pixel[X - RecLeft, Y - RecTop];
  353. if (MaskClr.A = 0) then Continue;
  354. ResetSumRecord(SumRec);
  355. I := Max(Y - RadiusI, RecTop);
  356. Q := I - (Y - RadiusI);
  357. for I := I to Min(Y + RadiusI, RecBottom) do
  358. with SumArray[X + I * ImageWidth] do
  359. begin
  360. Inc(SumRec.A, GaussLUT[Q][A]);
  361. Inc(SumRec.R, GaussLUT[Q][R]);
  362. Inc(SumRec.G, GaussLUT[Q][G]);
  363. Inc(SumRec.B, GaussLUT[Q][B]);
  364. Inc(SumRec.Sum, GaussLUT[Q][1]);
  365. Inc(Q);
  366. end;
  367. with ImagePixels[RowOffset + X] do
  368. if (MaskClr.A < 255) then
  369. begin
  370. Clr.A := SumRec.A div SumRec.Sum;
  371. Clr.R := DivMul255Table[Clr.A, SumRec.R div SumRec.Sum];
  372. Clr.G := DivMul255Table[Clr.A, SumRec.G div SumRec.Sum];
  373. Clr.B := DivMul255Table[Clr.A, SumRec.B div SumRec.Sum];
  374. BlendMemEx(Clr.ARGB, ARGB, MaskClr.A);
  375. end else
  376. begin
  377. A := SumRec.A div SumRec.Sum;
  378. R := DivMul255Table[A, SumRec.R div SumRec.Sum];
  379. G := DivMul255Table[A, SumRec.G div SumRec.Sum];
  380. B := DivMul255Table[A, SumRec.B div SumRec.Sum];
  381. end;
  382. end;
  383. Inc(RowOffset, ImageWidth);
  384. end;
  385. finally
  386. Mask.Free;
  387. end;
  388. end;
  389. procedure GaussianBlurGamma(Bitmap32: TBitmap32; Radius: TFloat);
  390. begin
  391. GaussianBlurGamma(Bitmap32, Radius, Bitmap32.BoundsRect);
  392. end;
  393. procedure GaussianBlurGamma(Bitmap32: TBitmap32; Radius: TFloat; const Bounds: TRect);
  394. var
  395. Q, I, J, X, Y, ImageWidth, RowOffset, RadiusI: Integer;
  396. RecLeft, RecTop, RecRight, RecBottom: Integer;
  397. ImagePixels: PColor32EntryArray;
  398. RadiusSq, RadiusRevSq, KernelSize: Integer;
  399. SumRec: TSumRecInt64;
  400. PreMulArray: array of TColor32Entry;
  401. SumArray: array of TSumRecInt64;
  402. GaussLUT: array of array of Cardinal;
  403. begin
  404. RadiusI := Round(Radius);
  405. if RadiusI < 1 then
  406. Exit
  407. else if RadiusI > 128 then
  408. RadiusI := 128; // nb: performance degrades exponentially with >> Radius
  409. // initialize the look-up-table ...
  410. KernelSize := RadiusI * 2 + 1;
  411. SetLength(GaussLUT, KernelSize);
  412. for I := 0 to KernelSize - 1 do
  413. SetLength(GaussLUT[I], ChannelSize);
  414. for I := 1 to RadiusI do
  415. begin
  416. RadiusRevSq := Round((Radius + 1 - I) * (Radius + 1 - I));
  417. for J := 0 to ChannelSizeMin1 do
  418. begin
  419. GaussLUT[RadiusI - I][J] := RadiusRevSq * J;
  420. GaussLUT[RadiusI + I][J] := GaussLUT[RadiusI - I][J];
  421. end;
  422. end;
  423. RadiusSq := Round((Radius + 1) * (Radius + 1));
  424. for J := 0 to ChannelSizeMin1 do
  425. GaussLUT[RadiusI][J] := RadiusSq * J;
  426. ImageWidth := Bitmap32.Width;
  427. SetLength(SumArray, ImageWidth * Bitmap32.Height);
  428. ImagePixels := PColor32EntryArray(Bitmap32.Bits);
  429. RecLeft := Max(Bounds.Left, 0);
  430. RecTop := Max(Bounds.Top, 0);
  431. RecRight := Min(Bounds.Right, ImageWidth - 1);
  432. RecBottom := Min(Bounds.Bottom, Bitmap32.Height - 1);
  433. RowOffset := RecTop * ImageWidth;
  434. SetLength(PreMulArray, Bitmap32.Width);
  435. for Y := RecTop to RecBottom do
  436. begin
  437. // initialize PreMulArray for the row ...
  438. Q := (Y * ImageWidth) + RecLeft;
  439. for X := RecLeft to RecRight do
  440. with ImagePixels[Q] do
  441. begin
  442. PreMulArray[X].A := A;
  443. PreMulArray[X].R := MulDiv255Table[GAMMA_DECODING_TABLE[R], A];
  444. PreMulArray[X].G := MulDiv255Table[GAMMA_DECODING_TABLE[G], A];
  445. PreMulArray[X].B := MulDiv255Table[GAMMA_DECODING_TABLE[B], A];
  446. Inc(Q);
  447. end;
  448. for X := RecLeft to RecRight do
  449. begin
  450. ResetSumRecord(SumRec);
  451. I := Max(X - RadiusI, RecLeft);
  452. Q := I - (X - RadiusI);
  453. for I := I to Min(X + RadiusI, RecRight) do
  454. with PreMulArray[I] do
  455. begin
  456. Inc(SumRec.A, GaussLUT[Q][A]);
  457. Inc(SumRec.R, GaussLUT[Q][R]);
  458. Inc(SumRec.G, GaussLUT[Q][G]);
  459. Inc(SumRec.B, GaussLUT[Q][B]);
  460. Inc(SumRec.Sum, GaussLUT[Q][1]);
  461. Inc(Q);
  462. end;
  463. Q := RowOffset + X;
  464. SumArray[Q] := Divide(SumRec);
  465. end;
  466. Inc(RowOffset, ImageWidth);
  467. end;
  468. RowOffset := RecTop * ImageWidth;
  469. for Y := RecTop to RecBottom do
  470. begin
  471. for X := RecLeft to RecRight do
  472. begin
  473. ResetSumRecord(SumRec);
  474. I := Max(Y - RadiusI, RecTop);
  475. Q := I - (Y - RadiusI);
  476. for I := I to Min(Y + RadiusI, RecBottom) do
  477. with SumArray[X + I * ImageWidth] do
  478. begin
  479. Inc(SumRec.A, GaussLUT[Q][A]);
  480. Inc(SumRec.R, GaussLUT[Q][R]);
  481. Inc(SumRec.G, GaussLUT[Q][G]);
  482. Inc(SumRec.B, GaussLUT[Q][B]);
  483. Inc(SumRec.Sum, GaussLUT[Q][1]);
  484. Inc(Q);
  485. end;
  486. with ImagePixels[RowOffset + X] do
  487. begin
  488. A := (SumRec.A div SumRec.Sum);
  489. R := GAMMA_ENCODING_TABLE[DivMul255Table[A, (SumRec.R div SumRec.Sum)]];
  490. G := GAMMA_ENCODING_TABLE[DivMul255Table[A, (SumRec.G div SumRec.Sum)]];
  491. B := GAMMA_ENCODING_TABLE[DivMul255Table[A, (SumRec.B div SumRec.Sum)]];
  492. end;
  493. end;
  494. Inc(RowOffset, ImageWidth);
  495. end;
  496. end;
  497. procedure GaussianBlurGamma(Bitmap32: TBitmap32; Radius: TFloat;
  498. const BlurRegion: TArrayOfFloatPoint);
  499. var
  500. Q, I, J, X, Y, ImageWidth, RowOffset, RadiusI: Integer;
  501. RecLeft, RecTop, RecRight, RecBottom: Integer;
  502. ImagePixels: PColor32EntryArray;
  503. RadiusSq, RadiusRevSq, KernelSize: Integer;
  504. SumRec: TSumRecInt64;
  505. SumArray: array of TSumRecInt64;
  506. GaussLUT: array of array of Cardinal;
  507. PreMulArray: array of TColor32Entry;
  508. Mask: TBitmap32;
  509. Clr, MaskClr: TColor32Entry;
  510. Pts: TArrayOfFloatPoint;
  511. Bounds: TRect;
  512. begin
  513. with PolygonBounds(BlurRegion) do
  514. Bounds := Rect(Floor(Left), Floor(Top), Ceil(Right), Ceil(Bottom));
  515. if Bounds.Left < 0 then Bounds.Left := 0;
  516. if Bounds.Top < 0 then Bounds.Top := 0;
  517. if Bounds.Right >= Bitmap32.Width then Bounds.Right := Bitmap32.Width - 1;
  518. if Bounds.Bottom >= Bitmap32.Height then Bounds.Bottom := Bitmap32.Height - 1;
  519. RadiusI := round(Radius);
  520. if (RadiusI < 1) or (Bounds.Right <= Bounds.Left) or (Bounds.Bottom <= Bounds.Top) then
  521. Exit
  522. else if RadiusI > 128 then
  523. RadiusI := 128; // nb: performance degrades exponentially with >> Radius
  524. Mask := TBitmap32.Create;
  525. try
  526. Mask.SetSize(Bounds.Right - Bounds.Left + 1, Bounds.Bottom - Bounds.Top + 1);
  527. SetLength(Pts, Length(BlurRegion));
  528. for I := 0 to High(BlurRegion) do
  529. begin
  530. Pts[I].X := BlurRegion[I].X - Bounds.Left;
  531. Pts[I].Y := BlurRegion[I].Y - Bounds.Top;
  532. end;
  533. PolygonFS(Mask, Pts, clWhite32);
  534. // initialize the look-up-table ...
  535. KernelSize := RadiusI * 2 + 1;
  536. SetLength(GaussLUT, KernelSize);
  537. for I := 0 to KernelSize - 1 do
  538. SetLength(GaussLUT[I], ChannelSize);
  539. for I := 1 to RadiusI do
  540. begin
  541. RadiusRevSq := Round((Radius + 1 - I) * (Radius + 1 - I));
  542. for J := 0 to ChannelSizeMin1 do
  543. begin
  544. GaussLUT[RadiusI - I][J] := RadiusRevSq * J;
  545. GaussLUT[RadiusI + I][J] := GaussLUT[RadiusI - I][J];
  546. end;
  547. end;
  548. RadiusSq := Round((Radius + 1) * (Radius + 1));
  549. for J := 0 to ChannelSizeMin1 do
  550. GaussLUT[RadiusI][J] := RadiusSq * J;
  551. ImageWidth := Bitmap32.Width;
  552. SetLength(SumArray, ImageWidth * Bitmap32.Height);
  553. ImagePixels := PColor32EntryArray(Bitmap32.Bits);
  554. RecLeft := Max(Bounds.Left, 0);
  555. RecTop := Max(Bounds.Top, 0);
  556. RecRight := Min(Bounds.Right, ImageWidth - 1);
  557. RecBottom := Min(Bounds.Bottom, Bitmap32.Height - 1);
  558. RowOffset := RecTop * ImageWidth;
  559. SetLength(PreMulArray, Bitmap32.Width);
  560. for Y := RecTop to RecBottom do
  561. begin
  562. // initialize PreMulArray for the current row ...
  563. Q := (Y * ImageWidth) + RecLeft;
  564. for X := RecLeft to RecRight do
  565. with ImagePixels[Q] do
  566. begin
  567. PreMulArray[X].A := A;
  568. PreMulArray[X].R := MulDiv255Table[GAMMA_DECODING_TABLE[R], A];
  569. PreMulArray[X].G := MulDiv255Table[GAMMA_DECODING_TABLE[G], A];
  570. PreMulArray[X].B := MulDiv255Table[GAMMA_DECODING_TABLE[B], A];
  571. Inc(Q);
  572. end;
  573. for X := RecLeft to RecRight do
  574. begin
  575. ResetSumRecord(SumRec);
  576. I := Max(X - RadiusI, RecLeft);
  577. Q := I - (X - RadiusI);
  578. for I := I to Min(X + RadiusI, RecRight) do
  579. with PreMulArray[I] do
  580. begin
  581. Inc(SumRec.A, GaussLUT[Q][A]);
  582. Inc(SumRec.R, GaussLUT[Q][R]);
  583. Inc(SumRec.G, GaussLUT[Q][G]);
  584. Inc(SumRec.B, GaussLUT[Q][B]);
  585. Inc(SumRec.Sum, GaussLUT[Q][1]);
  586. Inc(Q);
  587. end;
  588. Q := RowOffset + X;
  589. SumArray[Q] := Divide(SumRec);
  590. end;
  591. Inc(RowOffset, ImageWidth);
  592. end;
  593. RowOffset := RecTop * ImageWidth;
  594. for Y := RecTop to RecBottom do
  595. begin
  596. for X := RecLeft to RecRight do
  597. begin
  598. MaskClr.ARGB := Mask.Pixel[X - RecLeft, Y - RecTop];
  599. if (MaskClr.A = 0) then Continue;
  600. ResetSumRecord(SumRec);
  601. I := Max(Y - RadiusI, RecTop);
  602. Q := I - (Y - RadiusI);
  603. for I := I to Min(Y + RadiusI, RecBottom) do
  604. with SumArray[X + I * ImageWidth] do
  605. begin
  606. Inc(SumRec.A, GaussLUT[Q][A]);
  607. Inc(SumRec.R, GaussLUT[Q][R]);
  608. Inc(SumRec.G, GaussLUT[Q][G]);
  609. Inc(SumRec.B, GaussLUT[Q][B]);
  610. Inc(SumRec.Sum, GaussLUT[Q][1]);
  611. Inc(Q);
  612. end;
  613. with ImagePixels[RowOffset + X] do
  614. if (MaskClr.A < 255) then
  615. begin
  616. Clr.A := SumRec.A div SumRec.Sum;
  617. Clr.R := GAMMA_ENCODING_TABLE[DivMul255Table[Clr.A, SumRec.R div SumRec.Sum]];
  618. Clr.G := GAMMA_ENCODING_TABLE[DivMul255Table[Clr.A, SumRec.G div SumRec.Sum]];
  619. Clr.B := GAMMA_ENCODING_TABLE[DivMul255Table[Clr.A, SumRec.B div SumRec.Sum]];
  620. BlendMemEx(Clr.ARGB, ARGB, MaskClr.A);
  621. end else
  622. begin
  623. A := SumRec.A div SumRec.Sum;
  624. R := GAMMA_ENCODING_TABLE[DivMul255Table[A, SumRec.R div SumRec.Sum]];
  625. G := GAMMA_ENCODING_TABLE[DivMul255Table[A, SumRec.G div SumRec.Sum]];
  626. B := GAMMA_ENCODING_TABLE[DivMul255Table[A, SumRec.B div SumRec.Sum]];
  627. end;
  628. end;
  629. Inc(RowOffset, ImageWidth);
  630. end;
  631. finally
  632. Mask.Free;
  633. end;
  634. end;
  635. { FastBlur }
  636. procedure FastBlur(Bitmap32: TBitmap32; Radius: TFloat);
  637. begin
  638. FastBlur(Bitmap32, Radius, Bitmap32.BoundsRect);
  639. end;
  640. procedure FastBlur(Bitmap32: TBitmap32; Radius: TFloat; const Bounds: TRect);
  641. var
  642. LL, RR, TT, BB, XX, YY, I, J, X, Y, RadiusI, Passes: Integer;
  643. RecLeft, RecTop, RecRight, RecBottom: Integer;
  644. ImagePixel: PColor32Entry;
  645. SumRec: TSumRecord;
  646. ImgPixel: PColor32Entry;
  647. Pixels: array of TColor32Entry;
  648. begin
  649. if Radius < 1 then
  650. Exit
  651. else if Radius > 256 then
  652. Radius := 256;
  653. RadiusI := Round(Radius / Sqrt(-2 * Ln(COne255th)));
  654. if RadiusI < 2 then
  655. begin
  656. Passes := Round(Radius);
  657. RadiusI := 1;
  658. end else
  659. Passes := 3;
  660. RecLeft := Max(Bounds.Left, 0);
  661. RecTop := Max(Bounds.Top, 0);
  662. RecRight := Min(Bounds.Right, Bitmap32.Width - 1);
  663. RecBottom := Min(Bounds.Bottom, Bitmap32.Height - 1);
  664. SetLength(Pixels, Max(Bitmap32.Width, Bitmap32.Height) + 1);
  665. // pre-multiply alphas ...
  666. for Y := RecTop to RecBottom do
  667. begin
  668. ImgPixel := PColor32Entry(Bitmap32.ScanLine[Y]);
  669. Inc(ImgPixel, RecLeft);
  670. for X := RecLeft to RecRight do
  671. with ImgPixel^ do
  672. begin
  673. R := MulDiv255Table[R, A];
  674. G := MulDiv255Table[G, A];
  675. B := MulDiv255Table[B, A];
  676. Inc(ImgPixel);
  677. end;
  678. end;
  679. for I := 1 to Passes do
  680. begin
  681. // horizontal pass...
  682. for Y := RecTop to RecBottom do
  683. begin
  684. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[Y][RecLeft]);
  685. // fill the Pixels buffer with a copy of the row's pixels ...
  686. MoveLongword(ImagePixel^, Pixels[RecLeft], RecRight - RecLeft + 1);
  687. ResetSumRecord(SumRec);
  688. LL := RecLeft;
  689. RR := RecLeft + RadiusI;
  690. if RR > RecRight then RR := RecRight;
  691. // update first in row ...
  692. for XX := LL to RR do
  693. with Pixels[XX] do
  694. begin
  695. Inc(SumRec.A, A);
  696. Inc(SumRec.R, R);
  697. Inc(SumRec.G, G);
  698. Inc(SumRec.B, B);
  699. Inc(SumRec.Sum);
  700. end;
  701. ImagePixel^ := DivideToColor32(SumRec);
  702. // update the remaining pixels in the row ...
  703. for X := RecLeft + 1 to RecRight do
  704. begin
  705. Inc(ImagePixel);
  706. LL := X - RadiusI - 1;
  707. RR := X + RadiusI;
  708. if LL >= RecLeft then
  709. with Pixels[LL] do
  710. begin
  711. Dec(SumRec.A, A);
  712. Dec(SumRec.R, R);
  713. Dec(SumRec.G, G);
  714. Dec(SumRec.B, B);
  715. Dec(SumRec.Sum);
  716. end;
  717. if RR <= RecRight then
  718. with Pixels[RR] do
  719. begin
  720. Inc(SumRec.A, A);
  721. Inc(SumRec.R, R);
  722. Inc(SumRec.G, G);
  723. Inc(SumRec.B, B);
  724. Inc(SumRec.Sum);
  725. end;
  726. ImagePixel^ := DivideToColor32(SumRec);
  727. end;
  728. end;
  729. // vertical pass...
  730. for X := RecLeft to RecRight do
  731. begin
  732. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[RecTop][X]);
  733. for J := RecTop to RecBottom do
  734. begin
  735. Pixels[J] := ImagePixel^;
  736. Inc(ImagePixel, Bitmap32.Width);
  737. end;
  738. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[RecTop][X]);
  739. TT := RecTop;
  740. BB := RecTop + RadiusI;
  741. if BB > RecBottom then BB := RecBottom;
  742. ResetSumRecord(SumRec);
  743. // update first in col ...
  744. for YY := TT to BB do
  745. with Pixels[YY] do
  746. begin
  747. Inc(SumRec.A, A);
  748. Inc(SumRec.R, R);
  749. Inc(SumRec.G, G);
  750. Inc(SumRec.B, B);
  751. Inc(SumRec.Sum);
  752. end;
  753. ImagePixel^ := DivideToColor32(SumRec);
  754. // update remainder in col ...
  755. for Y := RecTop + 1 to RecBottom do
  756. begin
  757. Inc(ImagePixel, Bitmap32.Width);
  758. TT := Y - RadiusI - 1;
  759. BB := Y + RadiusI;
  760. if TT >= RecTop then
  761. with Pixels[TT] do
  762. begin
  763. Dec(SumRec.A, A);
  764. Dec(SumRec.R, R);
  765. Dec(SumRec.G, G);
  766. Dec(SumRec.B, B);
  767. Dec(SumRec.Sum);
  768. end;
  769. if BB <= RecBottom then
  770. with Pixels[BB] do
  771. begin
  772. Inc(SumRec.A, A);
  773. Inc(SumRec.R, R);
  774. Inc(SumRec.G, G);
  775. Inc(SumRec.B, B);
  776. Inc(SumRec.Sum);
  777. end;
  778. ImagePixel^ := DivideToColor32(SumRec);
  779. end;
  780. end;
  781. end;
  782. // extract alphas ...
  783. for Y := RecTop to RecBottom do
  784. begin
  785. ImgPixel := PColor32Entry(@Bitmap32.ScanLine[Y][RecLeft]);
  786. for X := RecLeft to RecRight do
  787. begin
  788. ImgPixel.R := DivMul255Table[ImgPixel.A, ImgPixel.R];
  789. ImgPixel.G := DivMul255Table[ImgPixel.A, ImgPixel.G];
  790. ImgPixel.B := DivMul255Table[ImgPixel.A, ImgPixel.B];
  791. Inc(ImgPixel);
  792. end;
  793. end;
  794. end;
  795. procedure FastBlur(Bitmap32: TBitmap32; Radius: TFloat; const BlurRegion: TArrayOfFloatPoint);
  796. var
  797. LL, RR, TT, BB, XX, YY, I, J, X, Y, RadiusI, Passes: Integer;
  798. RecLeft, RecTop, RecRight, RecBottom: Integer;
  799. ImagePixel: PColor32Entry;
  800. SumRec: TSumRecord;
  801. ImgPixel: PColor32Entry;
  802. Pixels: array of TSumRecord;
  803. Mask: TBitmap32;
  804. Clr, MaskClr: TColor32Entry;
  805. Pts: TArrayOfFloatPoint;
  806. Bounds: TRect;
  807. begin
  808. if Radius < 1 then
  809. Exit
  810. else if Radius > 256 then
  811. Radius := 256;
  812. RadiusI := Round(Radius / Sqrt(-2 * Ln(COne255th)));
  813. if RadiusI < 2 then
  814. begin
  815. Passes := Round(Radius);
  816. RadiusI := 1;
  817. end else
  818. Passes := 3;
  819. with PolygonBounds(BlurRegion) do
  820. Bounds := Rect(Floor(Left), Floor(Top), Ceil(Right), Ceil(Bottom));
  821. if Bounds.Left < 0 then Bounds.Left := 0;
  822. if Bounds.Top < 0 then Bounds.Top := 0;
  823. if Bounds.Right >= Bitmap32.Width then Bounds.Right := Bitmap32.Width - 1;
  824. if Bounds.Bottom >= Bitmap32.Height then Bounds.Bottom := Bitmap32.Height - 1;
  825. RecLeft := Max(Bounds.Left, 0);
  826. RecTop := Max(Bounds.Top, 0);
  827. RecRight := Min(Bounds.Right, Bitmap32.Width - 1);
  828. RecBottom := Min(Bounds.Bottom, Bitmap32.Height - 1);
  829. SetLength(Pixels, Max(Bitmap32.Width, Bitmap32.Height) + 1);
  830. // pre-multiply alphas ...
  831. for Y := RecTop to RecBottom do
  832. begin
  833. ImgPixel := PColor32Entry(Bitmap32.ScanLine[Y]);
  834. Inc(ImgPixel, RecLeft);
  835. for X := RecLeft to RecRight do
  836. begin
  837. ImgPixel.R := MulDiv255Table[ImgPixel.R, ImgPixel.A];
  838. ImgPixel.G := MulDiv255Table[ImgPixel.G, ImgPixel.A];
  839. ImgPixel.B := MulDiv255Table[ImgPixel.B, ImgPixel.A];
  840. Inc(ImgPixel);
  841. end;
  842. end;
  843. Mask := TBitmap32.Create;
  844. try
  845. Mask.SetSize(Bounds.Right - Bounds.Left + 1, Bounds.Bottom - Bounds.Top + 1);
  846. SetLength(Pts, Length(BlurRegion));
  847. for I := 0 to High(BlurRegion) do
  848. begin
  849. Pts[I].X := BlurRegion[I].X - Bounds.Left;
  850. Pts[I].Y := BlurRegion[I].Y - Bounds.Top;
  851. end;
  852. PolygonFS(Mask, Pts, clWhite32);
  853. for I := 1 to Passes do
  854. begin
  855. // horizontal pass...
  856. for Y := RecTop to RecBottom do
  857. begin
  858. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[Y][RecLeft]);
  859. // fill the Pixels buffer with a copy of the row's pixels ...
  860. for J := RecLeft to RecRight do
  861. begin
  862. MaskClr.ARGB := Mask.Pixel[J - RecLeft, Y - RecTop];
  863. if (MaskClr.A = 0) then
  864. begin
  865. Pixels[J].A := 0;
  866. Pixels[J].R := 0;
  867. Pixels[J].G := 0;
  868. Pixels[J].B := 0;
  869. Pixels[J].Sum := 0;
  870. end else
  871. with ImagePixel^ do
  872. begin
  873. Pixels[J].A := A;
  874. Pixels[J].R := R;
  875. Pixels[J].G := G;
  876. Pixels[J].B := B;
  877. Pixels[J].Sum := 1;
  878. end;
  879. Inc(ImagePixel);
  880. end;
  881. LL := RecLeft;
  882. RR := RecLeft + RadiusI;
  883. if RR > RecRight then RR := RecRight;
  884. ResetSumRecord(SumRec);
  885. // update first in row ...
  886. for XX := LL to RR do
  887. with Pixels[XX] do
  888. begin
  889. Inc(SumRec.A, A);
  890. Inc(SumRec.R, R);
  891. Inc(SumRec.G, G);
  892. Inc(SumRec.B, B);
  893. Inc(SumRec.Sum, Sum);
  894. end;
  895. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[Y][RecLeft]);
  896. MaskClr.ARGB := Mask.Pixel[0, Y - RecTop];
  897. if (MaskClr.A > 0) and (SumRec.Sum > 0) then
  898. ImagePixel^ := DivideToColor32(SumRec);
  899. // update the remaining pixels in the row ...
  900. for X := RecLeft + 1 to RecRight do
  901. begin
  902. Inc(ImagePixel);
  903. LL := X - RadiusI - 1;
  904. RR := X + RadiusI;
  905. if LL >= RecLeft then
  906. with Pixels[LL] do
  907. begin
  908. Dec(SumRec.A, A);
  909. Dec(SumRec.R, R);
  910. Dec(SumRec.G, G);
  911. Dec(SumRec.B, B);
  912. Dec(SumRec.Sum, Sum);
  913. end;
  914. if RR <= RecRight then
  915. with Pixels[RR] do
  916. begin
  917. Inc(SumRec.A, A);
  918. Inc(SumRec.R, R);
  919. Inc(SumRec.G, G);
  920. Inc(SumRec.B, B);
  921. Inc(SumRec.Sum, Sum);
  922. end;
  923. MaskClr.ARGB := Mask.Pixel[X - RecLeft, Y - RecTop];
  924. if (SumRec.Sum > 0) and (MaskClr.A = 255) then
  925. ImagePixel^ := DivideToColor32(SumRec);
  926. end;
  927. end;
  928. // vertical pass...
  929. for X := RecLeft to RecRight do
  930. begin
  931. // fill the Pixels buffer with a copy of the col's pixels ...
  932. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[RecTop][X]);
  933. for J := RecTop to RecBottom do
  934. begin
  935. MaskClr.ARGB := Mask.Pixel[X - RecLeft, J - RecTop];
  936. if (MaskClr.A = 0) then
  937. begin
  938. Pixels[J].A := 0;
  939. Pixels[J].R := 0;
  940. Pixels[J].G := 0;
  941. Pixels[J].B := 0;
  942. Pixels[J].Sum := 0;
  943. end else
  944. with ImagePixel^ do
  945. begin
  946. Pixels[J].A := A;
  947. Pixels[J].R := R;
  948. Pixels[J].G := G;
  949. Pixels[J].B := B;
  950. Pixels[J].Sum := 1;
  951. end;
  952. Inc(ImagePixel, Bitmap32.Width);
  953. end;
  954. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[RecTop][X]);
  955. TT := RecTop;
  956. BB := RecTop + RadiusI;
  957. if BB > RecBottom then BB := RecBottom;
  958. ResetSumRecord(SumRec);
  959. // update first in col ...
  960. for YY := TT to BB do
  961. with Pixels[YY] do
  962. begin
  963. Inc(SumRec.A, A);
  964. Inc(SumRec.R, R);
  965. Inc(SumRec.G, G);
  966. Inc(SumRec.B, B);
  967. Inc(SumRec.Sum, Sum);
  968. end;
  969. MaskClr.ARGB := Mask.Pixel[X - RecLeft, 0];
  970. if (MaskClr.A > 0) and (SumRec.Sum > 0) then
  971. ImagePixel^ := DivideToColor32(SumRec);
  972. // update remainder in col ...
  973. for Y := RecTop + 1 to RecBottom do
  974. begin
  975. Inc(ImagePixel, Bitmap32.Width);
  976. TT := Y - RadiusI - 1;
  977. BB := Y + RadiusI;
  978. if TT >= RecTop then
  979. with Pixels[TT] do
  980. begin
  981. Dec(SumRec.A, A);
  982. Dec(SumRec.R, R);
  983. Dec(SumRec.G, G);
  984. Dec(SumRec.B, B);
  985. Dec(SumRec.Sum, Sum);
  986. end;
  987. if BB <= RecBottom then
  988. with Pixels[BB] do
  989. begin
  990. Inc(SumRec.A, A);
  991. Inc(SumRec.R, R);
  992. Inc(SumRec.G, G);
  993. Inc(SumRec.B, B);
  994. Inc(SumRec.Sum, Sum);
  995. end;
  996. MaskClr.ARGB := Mask.Pixel[X - RecLeft, Y - RecTop];
  997. if (SumRec.Sum = 0) or (MaskClr.A = 0) then
  998. // do nothing
  999. else if (I = Passes) then
  1000. begin
  1001. Clr := DivideToColor32(SumRec);
  1002. BlendMemEx(Clr.ARGB, ImagePixel^.ARGB, MaskClr.A);
  1003. end
  1004. else if (MaskClr.A = 255) then
  1005. ImagePixel^ := DivideToColor32(SumRec);
  1006. end;
  1007. end;
  1008. end;
  1009. // extract alphas ...
  1010. for Y := RecTop to RecBottom do
  1011. begin
  1012. ImgPixel := PColor32Entry(Bitmap32.ScanLine[Y]);
  1013. Inc(ImgPixel, RecLeft);
  1014. for X := RecLeft to RecRight do
  1015. begin
  1016. ImgPixel.R := DivMul255Table[ImgPixel.A, ImgPixel.R];
  1017. ImgPixel.G := DivMul255Table[ImgPixel.A, ImgPixel.G];
  1018. ImgPixel.B := DivMul255Table[ImgPixel.A, ImgPixel.B];
  1019. Inc(ImgPixel);
  1020. end;
  1021. end;
  1022. finally
  1023. Mask.Free;
  1024. end;
  1025. end;
  1026. procedure FastBlurGamma(Bitmap32: TBitmap32; Radius: TFloat);
  1027. begin
  1028. FastBlurGamma(Bitmap32, Radius, Bitmap32.BoundsRect);
  1029. end;
  1030. procedure FastBlurGamma(Bitmap32: TBitmap32; Radius: TFloat; const Bounds: TRect);
  1031. var
  1032. LL, RR, TT, BB, XX, YY, I, J, X, Y, RadiusI, Passes: Integer;
  1033. RecLeft, RecTop, RecRight, RecBottom: Integer;
  1034. ImagePixel: PColor32Entry;
  1035. SumRec: TSumRecord;
  1036. ImgPixel: PColor32Entry;
  1037. Pixels: array of TColor32Entry;
  1038. begin
  1039. if Radius < 1 then
  1040. Exit
  1041. else if Radius > 256 then
  1042. Radius := 256;
  1043. RadiusI := Round(Radius / Sqrt(-2 * Ln(COne255th)));
  1044. if RadiusI < 2 then
  1045. begin
  1046. Passes := Round(Radius);
  1047. RadiusI := 1;
  1048. end else
  1049. Passes := 3;
  1050. RecLeft := Max(Bounds.Left, 0);
  1051. RecTop := Max(Bounds.Top, 0);
  1052. RecRight := Min(Bounds.Right, Bitmap32.Width - 1);
  1053. RecBottom := Min(Bounds.Bottom, Bitmap32.Height - 1);
  1054. SetLength(Pixels, Max(Bitmap32.Width, Bitmap32.Height) + 1);
  1055. // pre-multiply alphas ...
  1056. for Y := RecTop to RecBottom do
  1057. begin
  1058. ImgPixel := PColor32Entry(Bitmap32.ScanLine[Y]);
  1059. Inc(ImgPixel, RecLeft);
  1060. for X := RecLeft to RecRight do
  1061. with ImgPixel^ do
  1062. begin
  1063. R := MulDiv255Table[GAMMA_DECODING_TABLE[R], A];
  1064. G := MulDiv255Table[GAMMA_DECODING_TABLE[G], A];
  1065. B := MulDiv255Table[GAMMA_DECODING_TABLE[B], A];
  1066. Inc(ImgPixel);
  1067. end;
  1068. end;
  1069. for I := 1 to Passes do
  1070. begin
  1071. // horizontal pass...
  1072. for Y := RecTop to RecBottom do
  1073. begin
  1074. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[Y][RecLeft]);
  1075. // fill the Pixels buffer with a copy of the row's pixels ...
  1076. MoveLongword(ImagePixel^, Pixels[RecLeft], RecRight - RecLeft + 1);
  1077. ResetSumRecord(SumRec);
  1078. LL := RecLeft;
  1079. RR := RecLeft + RadiusI;
  1080. if RR > RecRight then RR := RecRight;
  1081. // update first in row ...
  1082. for XX := LL to RR do
  1083. with Pixels[XX] do
  1084. begin
  1085. Inc(SumRec.A, A);
  1086. Inc(SumRec.R, R);
  1087. Inc(SumRec.G, G);
  1088. Inc(SumRec.B, B);
  1089. Inc(SumRec.Sum);
  1090. end;
  1091. ImagePixel^ := DivideToColor32(SumRec);
  1092. // update the remaining pixels in the row ...
  1093. for X := RecLeft + 1 to RecRight do
  1094. begin
  1095. Inc(ImagePixel);
  1096. LL := X - RadiusI - 1;
  1097. RR := X + RadiusI;
  1098. if LL >= RecLeft then
  1099. with Pixels[LL] do
  1100. begin
  1101. Dec(SumRec.A, A);
  1102. Dec(SumRec.R, R);
  1103. Dec(SumRec.G, G);
  1104. Dec(SumRec.B, B);
  1105. Dec(SumRec.Sum);
  1106. end;
  1107. if RR <= RecRight then
  1108. with Pixels[RR] do
  1109. begin
  1110. Inc(SumRec.A, A);
  1111. Inc(SumRec.R, R);
  1112. Inc(SumRec.G, G);
  1113. Inc(SumRec.B, B);
  1114. Inc(SumRec.Sum);
  1115. end;
  1116. ImagePixel^ := DivideToColor32(SumRec);
  1117. end;
  1118. end;
  1119. // vertical pass...
  1120. for X := RecLeft to RecRight do
  1121. begin
  1122. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[RecTop][X]);
  1123. for J := RecTop to RecBottom do
  1124. begin
  1125. Pixels[J] := ImagePixel^;
  1126. Inc(ImagePixel, Bitmap32.Width);
  1127. end;
  1128. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[RecTop][X]);
  1129. TT := RecTop;
  1130. BB := RecTop + RadiusI;
  1131. if BB > RecBottom then BB := RecBottom;
  1132. ResetSumRecord(SumRec);
  1133. // update first in col ...
  1134. for YY := TT to BB do
  1135. with Pixels[YY] do
  1136. begin
  1137. Inc(SumRec.A, A);
  1138. Inc(SumRec.R, R);
  1139. Inc(SumRec.G, G);
  1140. Inc(SumRec.B, B);
  1141. Inc(SumRec.Sum);
  1142. end;
  1143. ImagePixel^ := DivideToColor32(SumRec);
  1144. // update remainder in col ...
  1145. for Y := RecTop + 1 to RecBottom do
  1146. begin
  1147. Inc(ImagePixel, Bitmap32.Width);
  1148. TT := Y - RadiusI - 1;
  1149. BB := Y + RadiusI;
  1150. if TT >= RecTop then
  1151. with Pixels[TT] do
  1152. begin
  1153. Dec(SumRec.A, A);
  1154. Dec(SumRec.R, R);
  1155. Dec(SumRec.G, G);
  1156. Dec(SumRec.B, B);
  1157. Dec(SumRec.Sum);
  1158. end;
  1159. if BB <= RecBottom then
  1160. with Pixels[BB] do
  1161. begin
  1162. Inc(SumRec.A, A);
  1163. Inc(SumRec.R, R);
  1164. Inc(SumRec.G, G);
  1165. Inc(SumRec.B, B);
  1166. Inc(SumRec.Sum);
  1167. end;
  1168. ImagePixel^ := DivideToColor32(SumRec);
  1169. end;
  1170. end;
  1171. end;
  1172. // extract alphas ...
  1173. for Y := RecTop to RecBottom do
  1174. begin
  1175. ImgPixel := PColor32Entry(@Bitmap32.ScanLine[Y][RecLeft]);
  1176. for X := RecLeft to RecRight do
  1177. begin
  1178. ImgPixel.R := GAMMA_ENCODING_TABLE[DivMul255Table[ImgPixel.A, ImgPixel.R]];
  1179. ImgPixel.G := GAMMA_ENCODING_TABLE[DivMul255Table[ImgPixel.A, ImgPixel.G]];
  1180. ImgPixel.B := GAMMA_ENCODING_TABLE[DivMul255Table[ImgPixel.A, ImgPixel.B]];
  1181. Inc(ImgPixel);
  1182. end;
  1183. end;
  1184. end;
  1185. procedure FastBlurGamma(Bitmap32: TBitmap32; Radius: TFloat; const BlurRegion: TArrayOfFloatPoint);
  1186. var
  1187. LL, RR, TT, BB, XX, YY, I, J, X, Y, RadiusI, Passes: Integer;
  1188. RecLeft, RecTop, RecRight, RecBottom: Integer;
  1189. ImagePixel: PColor32Entry;
  1190. SumRec: TSumRecord;
  1191. ImgPixel: PColor32Entry;
  1192. Pixels: array of TSumRecord;
  1193. Mask: TBitmap32;
  1194. Clr, MaskClr: TColor32Entry;
  1195. Pts: TArrayOfFloatPoint;
  1196. Bounds: TRect;
  1197. begin
  1198. if Radius < 1 then
  1199. Exit
  1200. else if Radius > 256 then
  1201. Radius := 256;
  1202. RadiusI := Round(Radius / Sqrt(-2 * Ln(COne255th)));
  1203. if RadiusI < 2 then
  1204. begin
  1205. Passes := Round(Radius);
  1206. RadiusI := 1;
  1207. end else
  1208. Passes := 3;
  1209. with PolygonBounds(BlurRegion) do
  1210. Bounds := Rect(Floor(Left), Floor(Top), Ceil(Right), Ceil(Bottom));
  1211. if Bounds.Left < 0 then Bounds.Left := 0;
  1212. if Bounds.Top < 0 then Bounds.Top := 0;
  1213. if Bounds.Right >= Bitmap32.Width then Bounds.Right := Bitmap32.Width - 1;
  1214. if Bounds.Bottom >= Bitmap32.Height then Bounds.Bottom := Bitmap32.Height - 1;
  1215. RecLeft := Max(Bounds.Left, 0);
  1216. RecTop := Max(Bounds.Top, 0);
  1217. RecRight := Min(Bounds.Right, Bitmap32.Width - 1);
  1218. RecBottom := Min(Bounds.Bottom, Bitmap32.Height - 1);
  1219. SetLength(Pixels, Max(Bitmap32.Width, Bitmap32.Height) + 1);
  1220. // pre-multiply alphas ...
  1221. for Y := RecTop to RecBottom do
  1222. begin
  1223. ImgPixel := PColor32Entry(Bitmap32.ScanLine[Y]);
  1224. Inc(ImgPixel, RecLeft);
  1225. for X := RecLeft to RecRight do
  1226. begin
  1227. ImgPixel.R := MulDiv255Table[GAMMA_DECODING_TABLE[ImgPixel.R], ImgPixel.A];
  1228. ImgPixel.G := MulDiv255Table[GAMMA_DECODING_TABLE[ImgPixel.G], ImgPixel.A];
  1229. ImgPixel.B := MulDiv255Table[GAMMA_DECODING_TABLE[ImgPixel.B], ImgPixel.A];
  1230. Inc(ImgPixel);
  1231. end;
  1232. end;
  1233. Mask := TBitmap32.Create;
  1234. try
  1235. Mask.SetSize(Bounds.Right - Bounds.Left + 1, Bounds.Bottom - Bounds.Top + 1);
  1236. SetLength(Pts, Length(BlurRegion));
  1237. for I := 0 to High(BlurRegion) do
  1238. begin
  1239. Pts[I].X := BlurRegion[I].X - Bounds.Left;
  1240. Pts[I].Y := BlurRegion[I].Y - Bounds.Top;
  1241. end;
  1242. PolygonFS(Mask, Pts, clWhite32);
  1243. for I := 1 to Passes do
  1244. begin
  1245. // horizontal pass...
  1246. for Y := RecTop to RecBottom do
  1247. begin
  1248. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[Y][RecLeft]);
  1249. // fill the Pixels buffer with a copy of the row's pixels ...
  1250. for J := RecLeft to RecRight do
  1251. begin
  1252. MaskClr.ARGB := Mask.Pixel[J - RecLeft, Y - RecTop];
  1253. if (MaskClr.A = 0) then
  1254. begin
  1255. Pixels[J].A := 0;
  1256. Pixels[J].R := 0;
  1257. Pixels[J].G := 0;
  1258. Pixels[J].B := 0;
  1259. Pixels[J].Sum := 0;
  1260. end else
  1261. with ImagePixel^ do
  1262. begin
  1263. Pixels[J].A := A;
  1264. Pixels[J].R := R;
  1265. Pixels[J].G := G;
  1266. Pixels[J].B := B;
  1267. Pixels[J].Sum := 1;
  1268. end;
  1269. Inc(ImagePixel);
  1270. end;
  1271. LL := RecLeft;
  1272. RR := RecLeft + RadiusI;
  1273. if RR > RecRight then RR := RecRight;
  1274. ResetSumRecord(SumRec);
  1275. // update first in row ...
  1276. for XX := LL to RR do
  1277. with Pixels[XX] do
  1278. begin
  1279. Inc(SumRec.A, A);
  1280. Inc(SumRec.R, R);
  1281. Inc(SumRec.G, G);
  1282. Inc(SumRec.B, B);
  1283. Inc(SumRec.Sum, Sum);
  1284. end;
  1285. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[Y][RecLeft]);
  1286. MaskClr.ARGB := Mask.Pixel[0, Y - RecTop];
  1287. if (MaskClr.A > 0) and (SumRec.Sum > 0) then
  1288. ImagePixel^ := DivideToColor32(SumRec);
  1289. // update the remaining pixels in the row ...
  1290. for X := RecLeft + 1 to RecRight do
  1291. begin
  1292. Inc(ImagePixel);
  1293. LL := X - RadiusI - 1;
  1294. RR := X + RadiusI;
  1295. if LL >= RecLeft then
  1296. with Pixels[LL] do
  1297. begin
  1298. Dec(SumRec.A, A);
  1299. Dec(SumRec.R, R);
  1300. Dec(SumRec.G, G);
  1301. Dec(SumRec.B, B);
  1302. Dec(SumRec.Sum, Sum);
  1303. end;
  1304. if RR <= RecRight then
  1305. with Pixels[RR] do
  1306. begin
  1307. Inc(SumRec.A, A);
  1308. Inc(SumRec.R, R);
  1309. Inc(SumRec.G, G);
  1310. Inc(SumRec.B, B);
  1311. Inc(SumRec.Sum, Sum);
  1312. end;
  1313. MaskClr.ARGB := Mask.Pixel[X - RecLeft, Y - RecTop];
  1314. if (SumRec.Sum > 0) and (MaskClr.A = 255) then
  1315. ImagePixel^ := DivideToColor32(SumRec);
  1316. end;
  1317. end;
  1318. // vertical pass...
  1319. for X := RecLeft to RecRight do
  1320. begin
  1321. // fill the Pixels buffer with a copy of the col's pixels ...
  1322. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[RecTop][X]);
  1323. for J := RecTop to RecBottom do
  1324. begin
  1325. MaskClr.ARGB := Mask.Pixel[X - RecLeft, J - RecTop];
  1326. if (MaskClr.A = 0) then
  1327. begin
  1328. Pixels[J].A := 0;
  1329. Pixels[J].R := 0;
  1330. Pixels[J].G := 0;
  1331. Pixels[J].B := 0;
  1332. Pixels[J].Sum := 0;
  1333. end else
  1334. with ImagePixel^ do
  1335. begin
  1336. Pixels[J].A := A;
  1337. Pixels[J].R := R;
  1338. Pixels[J].G := G;
  1339. Pixels[J].B := B;
  1340. Pixels[J].Sum := 1;
  1341. end;
  1342. Inc(ImagePixel, Bitmap32.Width);
  1343. end;
  1344. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[RecTop][X]);
  1345. TT := RecTop;
  1346. BB := RecTop + RadiusI;
  1347. if BB > RecBottom then BB := RecBottom;
  1348. ResetSumRecord(SumRec);
  1349. // update first in col ...
  1350. for YY := TT to BB do
  1351. with Pixels[YY] do
  1352. begin
  1353. Inc(SumRec.A, A);
  1354. Inc(SumRec.R, R);
  1355. Inc(SumRec.G, G);
  1356. Inc(SumRec.B, B);
  1357. Inc(SumRec.Sum, Sum);
  1358. end;
  1359. MaskClr.ARGB := Mask.Pixel[X - RecLeft, 0];
  1360. if (MaskClr.A > 0) and (SumRec.Sum > 0) then
  1361. ImagePixel^ := DivideToColor32(SumRec);
  1362. // update remainder in col ...
  1363. for Y := RecTop + 1 to RecBottom do
  1364. begin
  1365. Inc(ImagePixel, Bitmap32.Width);
  1366. TT := Y - RadiusI - 1;
  1367. BB := Y + RadiusI;
  1368. if TT >= RecTop then
  1369. with Pixels[TT] do
  1370. begin
  1371. Dec(SumRec.A, A);
  1372. Dec(SumRec.R, R);
  1373. Dec(SumRec.G, G);
  1374. Dec(SumRec.B, B);
  1375. Dec(SumRec.Sum, Sum);
  1376. end;
  1377. if BB <= RecBottom then
  1378. with Pixels[BB] do
  1379. begin
  1380. Inc(SumRec.A, A);
  1381. Inc(SumRec.R, R);
  1382. Inc(SumRec.G, G);
  1383. Inc(SumRec.B, B);
  1384. Inc(SumRec.Sum, Sum);
  1385. end;
  1386. MaskClr.ARGB := Mask.Pixel[X - RecLeft, Y - RecTop];
  1387. if (SumRec.Sum = 0) or (MaskClr.A = 0) then
  1388. // do nothing
  1389. else if (I = Passes) then
  1390. begin
  1391. Clr := DivideToColor32(SumRec);
  1392. BlendMemEx(Clr.ARGB, ImagePixel^.ARGB, MaskClr.A);
  1393. end
  1394. else if (MaskClr.A = 255) then
  1395. ImagePixel^ := DivideToColor32(SumRec);
  1396. end;
  1397. end;
  1398. end;
  1399. // extract alphas ...
  1400. for Y := RecTop to RecBottom do
  1401. begin
  1402. ImgPixel := PColor32Entry(Bitmap32.ScanLine[Y]);
  1403. Inc(ImgPixel, RecLeft);
  1404. for X := RecLeft to RecRight do
  1405. begin
  1406. ImgPixel.R := GAMMA_ENCODING_TABLE[DivMul255Table[ImgPixel.A, ImgPixel.R]];
  1407. ImgPixel.G := GAMMA_ENCODING_TABLE[DivMul255Table[ImgPixel.A, ImgPixel.G]];
  1408. ImgPixel.B := GAMMA_ENCODING_TABLE[DivMul255Table[ImgPixel.A, ImgPixel.B]];
  1409. Inc(ImgPixel);
  1410. end;
  1411. end;
  1412. finally
  1413. Mask.Free;
  1414. end;
  1415. end;
  1416. { MotionBlur }
  1417. procedure MotionBlur(Bitmap32: TBitmap32;
  1418. Dist, AngleDeg: TFloat; Bidirectional: Boolean = True);
  1419. begin
  1420. MotionBlur(Bitmap32, Dist, AngleDeg, Rectangle(Bitmap32.BoundsRect), Bidirectional);
  1421. end;
  1422. procedure MotionBlur(Bitmap32: TBitmap32; Dist, AngleDeg: TFloat;
  1423. const Bounds: TRect; Bidirectional: Boolean = True);
  1424. begin
  1425. MotionBlur(Bitmap32, Dist, AngleDeg, Rectangle(Bounds), Bidirectional);
  1426. end;
  1427. procedure MotionBlur(Bitmap32: TBitmap32; Dist, AngleDeg: TFloat;
  1428. const BlurRegion: TArrayOfFloatPoint; Bidirectional: Boolean = True);
  1429. var
  1430. LL, RR, XX, I, X, Y, RadiusI, Passes: Integer;
  1431. ImagePixel, ImagePixel2, ImagePixel3: PColor32Entry;
  1432. SumRec: TSumRecord;
  1433. Pixels: array of TSumRecord;
  1434. Mask: TBitmap32;
  1435. Clr, MaskClr: TColor32Entry;
  1436. Pts: TArrayOfFloatPoint;
  1437. Bounds: TRect;
  1438. Dx, Dy: Double;
  1439. Affine: TAffineTransformation;
  1440. BmpCutout: TBitmap32;
  1441. BmpRotated: TBitmap32;
  1442. FloatBounds: TFloatRect;
  1443. begin
  1444. if Dist < 1 then
  1445. Exit
  1446. else if Dist > 256 then
  1447. Dist := 256;
  1448. RadiusI := Round(Sqrt(-Dist * Dist / (2 * Ln(COne255th))));
  1449. if RadiusI < 2 then
  1450. begin
  1451. Passes := Round(Dist);
  1452. RadiusI := 1;
  1453. end else
  1454. Passes := 3;
  1455. Bounds := MakeRect(PolygonBounds(BlurRegion), rrOutside);
  1456. Bounds.Intersect(Rect(0, 0, Bitmap32.Width-1, Bitmap32.Height-1));
  1457. Affine := TAffineTransformation.Create;
  1458. BmpCutout := TBitmap32.Create;
  1459. BmpRotated := TBitmap32.Create;
  1460. BmpRotated.Resampler := TLinearResampler.Create(BmpRotated);
  1461. Mask := TBitmap32.Create;
  1462. try
  1463. // copy the region to be blurred into the BmpCutout image buffer ...
  1464. BmpCutout.SetSize(Bounds.Right - Bounds.Left, Bounds.Bottom - Bounds.Top);
  1465. for Y := 0 to BmpCutout.Height - 1 do
  1466. begin
  1467. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[Y + Bounds.Top][Bounds.Left]);
  1468. ImagePixel2 := PColor32Entry(BmpCutout.ScanLine[Y]);
  1469. MoveLongword(ImagePixel^, ImagePixel2^, BmpCutout.Width);
  1470. end;
  1471. // pre-multiply alphas in BmpCutout ...
  1472. for Y := 0 to BmpCutout.Height - 1 do
  1473. begin
  1474. ImagePixel := PColor32Entry(BmpCutout.ScanLine[Y]);
  1475. for X := 0 to BmpCutout.Width - 1 do
  1476. begin
  1477. ImagePixel.R := MulDiv255Table[ImagePixel.R, ImagePixel.A];
  1478. ImagePixel.G := MulDiv255Table[ImagePixel.G, ImagePixel.A];
  1479. ImagePixel.B := MulDiv255Table[ImagePixel.B, ImagePixel.A];
  1480. Inc(ImagePixel);
  1481. end;
  1482. end;
  1483. // Rotate BmpCutout into BmpRotated ...
  1484. Affine.SrcRect := FloatRect(BmpCutout.BoundsRect);
  1485. Affine.Rotate(180 - AngleDeg);
  1486. FloatBounds := Affine.GetTransformedBounds;
  1487. Mask.SetSize(Round(FloatBounds.Width) + 1, Round(FloatBounds.Height) + 1);
  1488. BmpRotated.SetSize(Mask.Width, Mask.Height);
  1489. Dx := FloatBounds.Left; Dy := FloatBounds.Top;
  1490. Affine.Translate(-Dx, -Dy);
  1491. Transform(BmpRotated, BmpCutout, Affine);
  1492. // Create a rotated mask ...
  1493. Affine.Clear;
  1494. Affine.Translate(-Bounds.Left, -Bounds.Top);
  1495. Affine.SrcRect := FloatRect(BmpCutout.BoundsRect);
  1496. Affine.Rotate(180 - AngleDeg);
  1497. Affine.Translate(-Dx, -Dy);
  1498. Pts := TransformPolygon(BlurRegion, Affine);
  1499. PolygonFS(Mask, Pts, clWhite32);
  1500. SetLength(Pixels, BmpRotated.Width);
  1501. // Now blur horizontally the rotated image ...
  1502. for I := 1 to Passes do
  1503. // Horizontal blur only ...
  1504. for Y := 0 to BmpRotated.Height - 1 do
  1505. begin
  1506. ImagePixel := PColor32Entry(BmpRotated.ScanLine[Y]);
  1507. // fill the Pixels buffer with a copy of the row's pixels ...
  1508. for X := 0 to BmpRotated.Width - 1 do
  1509. begin
  1510. MaskClr.ARGB := Mask.Pixel[X, Y];
  1511. if (MaskClr.A = 0) then
  1512. begin
  1513. Pixels[X].A := 0;
  1514. Pixels[X].R := 0;
  1515. Pixels[X].G := 0;
  1516. Pixels[X].B := 0;
  1517. Pixels[X].Sum := 0;
  1518. end else
  1519. with ImagePixel^ do
  1520. begin
  1521. Pixels[X].A := A;
  1522. Pixels[X].R := R;
  1523. Pixels[X].G := G;
  1524. Pixels[X].B := B;
  1525. Pixels[X].Sum := 1;
  1526. end;
  1527. Inc(ImagePixel);
  1528. end;
  1529. LL := 0;
  1530. RR := RadiusI;
  1531. if RR >= BmpRotated.Width then
  1532. RR := BmpRotated.Width - 1;
  1533. ResetSumRecord(SumRec);
  1534. // update first in row ...
  1535. for XX := LL to RR do
  1536. with Pixels[XX] do
  1537. begin
  1538. Inc(SumRec.A, A);
  1539. Inc(SumRec.R, R);
  1540. Inc(SumRec.G, G);
  1541. Inc(SumRec.B, B);
  1542. Inc(SumRec.Sum, Sum);
  1543. end;
  1544. ImagePixel := PColor32Entry(BmpRotated.ScanLine[Y]);
  1545. MaskClr.ARGB := Mask.Pixel[0, Y];
  1546. if (MaskClr.A > 0) and (SumRec.Sum > 0) then
  1547. ImagePixel^ := DivideToColor32(SumRec);
  1548. // update the remaining pixels in the row ...
  1549. for X := 1 to BmpRotated.Width - 1 do
  1550. begin
  1551. Inc(ImagePixel);
  1552. if Bidirectional then
  1553. LL := X - RadiusI - 1
  1554. else
  1555. LL := X - 1;
  1556. RR := X + RadiusI;
  1557. if LL >= 0 then
  1558. with Pixels[LL] do
  1559. begin
  1560. Dec(SumRec.A, A);
  1561. Dec(SumRec.R, R);
  1562. Dec(SumRec.G, G);
  1563. Dec(SumRec.B, B);
  1564. Dec(SumRec.Sum, Sum);
  1565. end;
  1566. if RR < BmpRotated.Width then
  1567. with Pixels[RR] do
  1568. begin
  1569. Inc(SumRec.A, A);
  1570. Inc(SumRec.R, R);
  1571. Inc(SumRec.G, G);
  1572. Inc(SumRec.B, B);
  1573. Inc(SumRec.Sum, Sum);
  1574. end;
  1575. MaskClr.ARGB := Mask.Pixel[X, Y];
  1576. if (SumRec.Sum = 0) or (MaskClr.A = 0) then
  1577. Continue
  1578. else
  1579. if (I = Passes) then
  1580. begin
  1581. Clr := DivideToColor32(SumRec);
  1582. BlendMemEx(Clr.ARGB, ImagePixel^.ARGB, MaskClr.A);
  1583. end else
  1584. if (MaskClr.A = 255) then
  1585. ImagePixel^ := DivideToColor32(SumRec);
  1586. end;
  1587. end;
  1588. // un-rotate the now blurred image back into BmpCutout ...
  1589. Affine.Clear;
  1590. Affine.SrcRect := FloatRect(BmpRotated.BoundsRect);
  1591. Affine.Translate(Dx, Dy);
  1592. Affine.Rotate(AngleDeg + 180);
  1593. Transform(BmpCutout, BmpRotated, Affine);
  1594. // extract alphas ...
  1595. for Y := 0 to BmpCutout.Height - 1 do
  1596. begin
  1597. ImagePixel := PColor32Entry(BmpCutout.ScanLine[Y]);
  1598. for X := 0 to BmpCutout.Width - 1 do
  1599. begin
  1600. ImagePixel.R := DivMul255Table[ImagePixel.A, ImagePixel.R];
  1601. ImagePixel.G := DivMul255Table[ImagePixel.A, ImagePixel.G];
  1602. ImagePixel.B := DivMul255Table[ImagePixel.A, ImagePixel.B];
  1603. Inc(ImagePixel);
  1604. end;
  1605. end;
  1606. // Create an un-rotated mask and copy masked pixels from BmpCutout
  1607. // back to the original image (Bitmap32) ...
  1608. Mask.SetSize(BmpCutout.Width, BmpCutout.Height);
  1609. Pts := TranslatePolygon(BlurRegion, -Bounds.Left, -Bounds.Top);
  1610. PolygonFS(Mask, Pts, clWhite32);
  1611. for Y := 0 to BmpCutout.Height - 1 do
  1612. begin
  1613. ImagePixel := PColor32Entry(BmpCutout.ScanLine[Y]);
  1614. ImagePixel2 := PColor32Entry(Mask.ScanLine[Y]);
  1615. ImagePixel3 := PColor32Entry(@Bitmap32.ScanLine[Y + Bounds.Top][Bounds.Left]);
  1616. for X := 0 to BmpCutout.Width - 1 do
  1617. begin
  1618. if ImagePixel2.A > 0 then
  1619. ImagePixel3.ARGB := ImagePixel.ARGB;
  1620. Inc(ImagePixel);
  1621. Inc(ImagePixel2);
  1622. Inc(ImagePixel3);
  1623. end;
  1624. end;
  1625. finally
  1626. Affine.Free;
  1627. BmpCutout.Free;
  1628. BmpRotated.Free;
  1629. Mask.Free;
  1630. end;
  1631. end;
  1632. procedure MotionBlurGamma(Bitmap32: TBitmap32;
  1633. Dist, AngleDeg: TFloat; Bidirectional: Boolean = True);
  1634. begin
  1635. MotionBlurGamma(Bitmap32, Dist, AngleDeg, Rectangle(Bitmap32.BoundsRect), Bidirectional);
  1636. end;
  1637. procedure MotionBlurGamma(Bitmap32: TBitmap32; Dist, AngleDeg: TFloat;
  1638. const Bounds: TRect; Bidirectional: Boolean = True);
  1639. begin
  1640. MotionBlurGamma(Bitmap32, Dist, AngleDeg, Rectangle(Bounds), Bidirectional);
  1641. end;
  1642. procedure MotionBlurGamma(Bitmap32: TBitmap32; Dist, AngleDeg: TFloat;
  1643. const BlurRegion: TArrayOfFloatPoint; Bidirectional: Boolean = True);
  1644. var
  1645. LL, RR, XX, I, X, Y, RadiusI, Passes: Integer;
  1646. ImagePixel, ImagePixel2, ImagePixel3: PColor32Entry;
  1647. SumRec: TSumRecord;
  1648. Pixels: array of TSumRecord;
  1649. Mask: TBitmap32;
  1650. Clr, MaskClr: TColor32Entry;
  1651. Pts: TArrayOfFloatPoint;
  1652. Bounds: TRect;
  1653. Dx, Dy: Double;
  1654. Affine: TAffineTransformation;
  1655. BmpCutout: TBitmap32;
  1656. BmpRotated: TBitmap32;
  1657. FloatBounds: TFloatRect;
  1658. begin
  1659. if Dist < 1 then
  1660. Exit
  1661. else if Dist > 256 then
  1662. Dist := 256;
  1663. RadiusI := Round(Sqrt(-Dist * Dist / (2 * Ln(COne255th))));
  1664. if RadiusI < 2 then
  1665. begin
  1666. Passes := Round(Dist);
  1667. RadiusI := 1;
  1668. end else
  1669. Passes := 3;
  1670. Bounds := MakeRect(PolygonBounds(BlurRegion), rrOutside);
  1671. Bounds.Intersect(Rect(0, 0, Bitmap32.Width-1, Bitmap32.Height-1));
  1672. Affine := TAffineTransformation.Create;
  1673. BmpCutout := TBitmap32.Create;
  1674. BmpRotated := TBitmap32.Create;
  1675. BmpRotated.Resampler := TLinearResampler.Create(BmpRotated);
  1676. Mask := TBitmap32.Create;
  1677. try
  1678. // copy the region to be blurred into the BmpCutout image buffer ...
  1679. BmpCutout.SetSize(Bounds.Right - Bounds.Left, Bounds.Bottom - Bounds.Top);
  1680. for Y := 0 to BmpCutout.Height - 1 do
  1681. begin
  1682. ImagePixel := PColor32Entry(@Bitmap32.ScanLine[Y + Bounds.Top][Bounds.Left]);
  1683. ImagePixel2 := PColor32Entry(BmpCutout.ScanLine[Y]);
  1684. MoveLongword(ImagePixel^, ImagePixel2^, BmpCutout.Width);
  1685. end;
  1686. // pre-multiply alphas in BmpCutout ...
  1687. for Y := 0 to BmpCutout.Height - 1 do
  1688. begin
  1689. ImagePixel := PColor32Entry(BmpCutout.ScanLine[Y]);
  1690. for X := 0 to BmpCutout.Width - 1 do
  1691. begin
  1692. ImagePixel.R := MulDiv255Table[GAMMA_DECODING_TABLE[ImagePixel.R], ImagePixel.A];
  1693. ImagePixel.G := MulDiv255Table[GAMMA_DECODING_TABLE[ImagePixel.G], ImagePixel.A];
  1694. ImagePixel.B := MulDiv255Table[GAMMA_DECODING_TABLE[ImagePixel.B], ImagePixel.A];
  1695. Inc(ImagePixel);
  1696. end;
  1697. end;
  1698. // Rotate BmpCutout into BmpRotated ...
  1699. Affine.SrcRect := FloatRect(BmpCutout.BoundsRect);
  1700. Affine.Rotate(180 - AngleDeg);
  1701. FloatBounds := Affine.GetTransformedBounds;
  1702. Mask.SetSize(Round(FloatBounds.Width) + 1, Round(FloatBounds.Height) + 1);
  1703. BmpRotated.SetSize(Mask.Width, Mask.Height);
  1704. Dx := FloatBounds.Left; Dy := FloatBounds.Top;
  1705. Affine.Translate(-Dx, -Dy);
  1706. Transform(BmpRotated, BmpCutout, Affine);
  1707. // Create a rotated mask ...
  1708. Affine.Clear;
  1709. Affine.Translate(-Bounds.Left, -Bounds.Top);
  1710. Affine.SrcRect := FloatRect(BmpCutout.BoundsRect);
  1711. Affine.Rotate(180 - AngleDeg);
  1712. Affine.Translate(-Dx, -Dy);
  1713. Pts := TransformPolygon(BlurRegion, Affine);
  1714. PolygonFS(Mask, Pts, clWhite32);
  1715. SetLength(Pixels, BmpRotated.Width);
  1716. // Now blur horizontally the rotated image ...
  1717. for I := 1 to Passes do
  1718. // Horizontal blur only ...
  1719. for Y := 0 to BmpRotated.Height - 1 do
  1720. begin
  1721. ImagePixel := PColor32Entry(BmpRotated.ScanLine[Y]);
  1722. // fill the Pixels buffer with a copy of the row's pixels ...
  1723. for X := 0 to BmpRotated.Width - 1 do
  1724. begin
  1725. MaskClr.ARGB := Mask.Pixel[X, Y];
  1726. if (MaskClr.A = 0) then
  1727. begin
  1728. Pixels[X].A := 0;
  1729. Pixels[X].R := 0;
  1730. Pixels[X].G := 0;
  1731. Pixels[X].B := 0;
  1732. Pixels[X].Sum := 0;
  1733. end else
  1734. with ImagePixel^ do
  1735. begin
  1736. Pixels[X].A := A;
  1737. Pixels[X].R := R;
  1738. Pixels[X].G := G;
  1739. Pixels[X].B := B;
  1740. Pixels[X].Sum := 1;
  1741. end;
  1742. Inc(ImagePixel);
  1743. end;
  1744. LL := 0;
  1745. RR := RadiusI;
  1746. if RR >= BmpRotated.Width then RR := BmpRotated.Width - 1;
  1747. ResetSumRecord(SumRec);
  1748. // update first in row ...
  1749. for XX := LL to RR do
  1750. with Pixels[XX] do
  1751. begin
  1752. Inc(SumRec.A, A);
  1753. Inc(SumRec.R, R);
  1754. Inc(SumRec.G, G);
  1755. Inc(SumRec.B, B);
  1756. Inc(SumRec.Sum, Sum);
  1757. end;
  1758. ImagePixel := PColor32Entry(BmpRotated.ScanLine[Y]);
  1759. MaskClr.ARGB := Mask.Pixel[0, Y];
  1760. if (MaskClr.A > 0) and (SumRec.Sum > 0) then
  1761. ImagePixel^ := DivideToColor32(SumRec);
  1762. // update the remaining pixels in the row ...
  1763. for X := 1 to BmpRotated.Width - 1 do
  1764. begin
  1765. Inc(ImagePixel);
  1766. if Bidirectional then
  1767. LL := X - RadiusI - 1
  1768. else
  1769. LL := X - 1;
  1770. RR := X + RadiusI;
  1771. if LL >= 0 then
  1772. with Pixels[LL] do
  1773. begin
  1774. Dec(SumRec.A, A);
  1775. Dec(SumRec.R, R);
  1776. Dec(SumRec.G, G);
  1777. Dec(SumRec.B, B);
  1778. Dec(SumRec.Sum, Sum);
  1779. end;
  1780. if RR < BmpRotated.Width then
  1781. with Pixels[RR] do
  1782. begin
  1783. Inc(SumRec.A, A);
  1784. Inc(SumRec.R, R);
  1785. Inc(SumRec.G, G);
  1786. Inc(SumRec.B, B);
  1787. Inc(SumRec.Sum, Sum);
  1788. end;
  1789. MaskClr.ARGB := Mask.Pixel[X, Y];
  1790. if (SumRec.Sum = 0) or (MaskClr.A = 0) then
  1791. Continue
  1792. else
  1793. if (I = Passes) then
  1794. begin
  1795. Clr := DivideToColor32(SumRec);
  1796. BlendMemEx(Clr.ARGB, ImagePixel^.ARGB, MaskClr.A);
  1797. end else
  1798. if (MaskClr.A = 255) then
  1799. ImagePixel^ := DivideToColor32(SumRec);
  1800. end;
  1801. end;
  1802. // un-rotate the now blurred image back into BmpCutout ...
  1803. Affine.Clear;
  1804. Affine.SrcRect := FloatRect(BmpRotated.BoundsRect);
  1805. Affine.Translate(Dx, Dy);
  1806. Affine.Rotate(AngleDeg + 180);
  1807. Transform(BmpCutout, BmpRotated, Affine);
  1808. // extract alphas ...
  1809. for Y := 0 to BmpCutout.Height - 1 do
  1810. begin
  1811. ImagePixel := PColor32Entry(BmpCutout.ScanLine[Y]);
  1812. for X := 0 to BmpCutout.Width - 1 do
  1813. begin
  1814. ImagePixel.R := GAMMA_ENCODING_TABLE[DivMul255Table[ImagePixel.A, ImagePixel.R]];
  1815. ImagePixel.G := GAMMA_ENCODING_TABLE[DivMul255Table[ImagePixel.A, ImagePixel.G]];
  1816. ImagePixel.B := GAMMA_ENCODING_TABLE[DivMul255Table[ImagePixel.A, ImagePixel.B]];
  1817. Inc(ImagePixel);
  1818. end;
  1819. end;
  1820. // Create an un-rotated mask and copy masked pixels from BmpCutout
  1821. // back to the original image (Bitmap32) ...
  1822. Mask.SetSize(BmpCutout.Width, BmpCutout.Height);
  1823. Pts := TranslatePolygon(BlurRegion, -Bounds.Left, -Bounds.Top);
  1824. PolygonFS(Mask, Pts, clWhite32);
  1825. for Y := 0 to BmpCutout.Height - 1 do
  1826. begin
  1827. ImagePixel := PColor32Entry(BmpCutout.ScanLine[Y]);
  1828. ImagePixel2 := PColor32Entry(Mask.ScanLine[Y]);
  1829. ImagePixel3 := PColor32Entry(@Bitmap32.ScanLine[Y + Bounds.Top][Bounds.Left]);
  1830. for X := 0 to BmpCutout.Width - 1 do
  1831. begin
  1832. if ImagePixel2.A > 0 then
  1833. ImagePixel3.ARGB := ImagePixel.ARGB;
  1834. Inc(ImagePixel);
  1835. Inc(ImagePixel2);
  1836. Inc(ImagePixel3);
  1837. end;
  1838. end;
  1839. finally
  1840. Affine.Free;
  1841. BmpCutout.Free;
  1842. BmpRotated.Free;
  1843. Mask.Free;
  1844. end;
  1845. end;
  1846. end.