Img32.Extra.pas 78 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643
  1. unit Img32.Extra;
  2. (*******************************************************************************
  3. * Author : Angus Johnson *
  4. * Version : 4.8 *
  5. * Date : 10 January 2025 *
  6. * Website : http://www.angusj.com *
  7. * Copyright : Angus Johnson 2019-2025 *
  8. * Purpose : Miscellaneous routines that don't belong in other modules. *
  9. * License : http://www.boost.org/LICENSE_1_0.txt *
  10. *******************************************************************************)
  11. interface
  12. {$I Img32.inc}
  13. uses
  14. SysUtils, Classes, Math, Types,
  15. Img32, Img32.Draw, Img32.Vector;
  16. type
  17. TButtonShape = (bsRound, bsSquare, bsDiamond);
  18. TButtonAttribute = (baShadow, ba3D, baEraseBeneath);
  19. TButtonAttributes = set of TButtonAttribute;
  20. procedure DrawEdge(img: TImage32; const rec: TRect;
  21. topLeftColor, bottomRightColor: TColor32; penWidth: double = 1.0); overload;
  22. procedure DrawEdge(img: TImage32; const rec: TRectD;
  23. topLeftColor, bottomRightColor: TColor32; penWidth: double = 1.0); overload;
  24. procedure DrawEdge(img: TImage32; const path: TPathD;
  25. topLeftColor, bottomRightColor: TColor32;
  26. penWidth: double = 1.0; closePath: Boolean = true); overload;
  27. //DrawShadowRect: is **much** faster than DrawShadow
  28. procedure DrawShadowRect(img: TImage32; const rec: TRect; depth: double;
  29. angle: double = angle45; color: TColor32 = $80000000);
  30. procedure DrawShadow(img: TImage32; const polygon: TPathD;
  31. fillRule: TFillRule; depth: double; angleRads: double = angle45;
  32. color: TColor32 = $80000000; cutoutInsideShadow: Boolean = false); overload;
  33. procedure DrawShadow(img: TImage32; const polygons: TPathsD;
  34. fillRule: TFillRule; depth: double; angleRads: double = angle45;
  35. color: TColor32 = $80000000; cutoutInsideShadow: Boolean = false); overload;
  36. procedure DrawGlow(img: TImage32; const polygon: TPathD;
  37. fillRule: TFillRule; color: TColor32; blurRadius: integer); overload;
  38. procedure DrawGlow(img: TImage32; const polygons: TPathsD;
  39. fillRule: TFillRule; color: TColor32; blurRadius: integer); overload;
  40. //FloodFill: If no CompareFunc is provided, FloodFill will fill whereever
  41. //adjoining pixels exactly match the starting pixel - Point(x,y).
  42. procedure FloodFill(img: TImage32; x, y: Integer; newColor: TColor32;
  43. tolerance: Byte = 0; compareFunc: TCompareFunctionEx = nil);
  44. procedure FastGaussianBlur(img: TImage32;
  45. const rec: TRect; stdDev: integer; repeats: integer = 2); overload;
  46. procedure FastGaussianBlur(img: TImage32;
  47. const rec: TRect; stdDevX, stdDevY: integer; repeats: integer); overload;
  48. procedure GaussianBlur(img: TImage32; rec: TRect; radius: Integer);
  49. //Emboss: A smaller radius is sharper. Increasing depth increases contrast.
  50. //Luminance changes grayscale balance (unless preserveColor = true)
  51. procedure Emboss(img: TImage32; radius: Integer = 1; depth: Integer = 10;
  52. luminance: Integer = 75; preserveColor: Boolean = false);
  53. //Sharpen: Radius range is 1 - 10; amount range is 1 - 50.<br>
  54. //see https://en.wikipedia.org/wiki/Unsharp_masking
  55. procedure Sharpen(img: TImage32; radius: Integer = 2; amount: Integer = 10);
  56. //HatchBackground: Assumes the current image is semi-transparent.
  57. procedure HatchBackground(img: TImage32; color1: TColor32 = clWhite32;
  58. color2: TColor32= $FFE8E8E8; hatchSize: Integer = 10); overload;
  59. procedure HatchBackground(img: TImage32; const rec: TRect;
  60. color1: TColor32 = clWhite32; color2: TColor32= $FFE8E8E8;
  61. hatchSize: Integer = 10); overload;
  62. procedure GridBackground(img: TImage32; majorInterval, minorInterval: integer;
  63. fillColor: TColor32 = clWhite32;
  64. majColor: TColor32 = $30000000; minColor: TColor32 = $20000000);
  65. procedure ReplaceExactColor(img: TImage32; oldColor, newColor: TColor32);
  66. //RemoveColor: Removes the specified color from the image, even from
  67. //pixels that are a blend of colors including the specified color.<br>
  68. //see https://stackoverflow.com/questions/9280902/
  69. procedure RemoveColor(img: TImage32; color: TColor32);
  70. //FilterOnColor: Removes everything not nearly matching 'color'
  71. //This uses an algorithm that's very similar to the one in RemoveColor.
  72. procedure FilterOnColor(img: TImage32; color: TColor32);
  73. procedure FilterOnExactColor(img: TImage32; color: TColor32);
  74. procedure FilterOnAlpha(img: TImage32; alpha: byte; tolerance: byte);
  75. //RedEyeRemove: Removes 'red eye' from flash photo images.
  76. procedure RedEyeRemove(img: TImage32; const rect: TRect);
  77. procedure PencilEffect(img: TImage32; intensity: integer = 0);
  78. procedure TraceContours(img: TImage32; intensity: integer);
  79. procedure EraseInsidePath(img: TImage32;
  80. const path: TPathD; fillRule: TFillRule);
  81. procedure EraseInsidePaths(img: TImage32;
  82. const paths: TPathsD; fillRule: TFillRule);
  83. procedure EraseOutsidePath(img: TImage32; const path: TPathD;
  84. fillRule: TFillRule; const outsideBounds: TRect);
  85. procedure EraseOutsidePaths(img: TImage32; const paths: TPathsD;
  86. fillRule: TFillRule; const outsideBounds: TRect;
  87. rendererCache: TCustomRendererCache = nil); overload;
  88. procedure Draw3D(img: TImage32; const polygon: TPathD;
  89. fillRule: TFillRule; height, blurRadius: double;
  90. colorLt: TColor32 = $DDFFFFFF; colorDk: TColor32 = $80000000;
  91. angleRads: double = angle225); overload;
  92. procedure Draw3D(img: TImage32; const polygons: TPathsD;
  93. fillRule: TFillRule; height, blurRadius: double;
  94. colorLt: TColor32 = $DDFFFFFF; colorDk: TColor32 = $80000000;
  95. angleRads: double = angle225); overload;
  96. function RainbowColor(fraction: double; luminance: byte = 128): TColor32;
  97. function GradientColor(color1, color2: TColor32; frac: single): TColor32;
  98. function MakeDarker(color: TColor32; percent: cardinal): TColor32;
  99. function MakeLighter(color: TColor32; percent: cardinal): TColor32;
  100. function DrawButton(img: TImage32; const pt: TPointD;
  101. size: double; color: TColor32 = clNone32;
  102. buttonShape: TButtonShape = bsRound;
  103. buttonAttributes: TButtonAttributes = [baShadow, ba3D, baEraseBeneath]): TPathD;
  104. // RamerDouglasPeucker: simplifies paths, recursively removing vertices where
  105. // they deviate no more than 'epsilon' from their adjacent vertices.
  106. function RamerDouglasPeucker(const path: TPathD;
  107. epsilon: double): TPathD; overload;
  108. function RamerDouglasPeucker(const paths: TPathsD;
  109. epsilon: double): TPathsD; overload;
  110. {$IFDEF USE_OLD_SIMPLIFYPATHS}
  111. // SimplifyPath: Better than RDP when simplifying closed paths
  112. function SimplifyPath(const path: TPathD;
  113. shapeTolerance: double = 0.1; isOpenPath: Boolean = false): TPathD;
  114. function SimplifyPaths(const paths: TPathsD;
  115. shapeTolerance: double = 0.1; isOpenPath: Boolean = false): TPathsD;
  116. {$ELSE}
  117. // SimplifyPath: Better than RDP when simplifying closed paths
  118. function SimplifyPath(const path: TPathD;
  119. shapeTolerance: double = 0.1; isClosedPath: Boolean = true): TPathD;
  120. function SimplifyPaths(const paths: TPathsD;
  121. shapeTolerance: double = 0.1; isClosedPath: Boolean = true): TPathsD;
  122. {$ENDIF}
  123. // SimplifyPathEx: this is particularly useful following Vectorize()
  124. // because it also removes very short zig-zag segments
  125. function SimplifyPathEx(const path: TPathD; shapeTolerance: double): TPathD;
  126. function SimplifyPathsEx(const paths: TPathsD; shapeTolerance: double): TPathsD;
  127. // SmoothToCubicBezier and SmoothToCubicBezier2 have been deprecated in
  128. // favour of SmoothPath that's much simpler
  129. function SmoothToCubicBezier(const path: TPathD;
  130. pathIsClosed: Boolean; maxOffset: integer = 0): TPathD; overload; deprecated;
  131. function SmoothToCubicBezier(const paths: TPathsD;
  132. pathIsClosed: Boolean; maxOffset: integer = 0): TPathsD; overload; deprecated;
  133. function SmoothToCubicBezier2(const path: TPathD;
  134. pathIsClosed: Boolean; maxOffset: integer = 0): TPathD; overload; deprecated;
  135. function SmoothToCubicBezier2(const paths: TPathsD;
  136. pathIsClosed: Boolean; maxOffset: integer = 0): TPathsD; overload; deprecated;
  137. // SmoothPath - smooths a path using bicubic interpolation
  138. // tension (range -1 to 1): from least to most curve constraint
  139. function SmoothPath(const path: TPathD; isClosedPath: Boolean;
  140. tension: double = 0; shapeTolerance: double = 0.1): TPathD;
  141. function SmoothPaths(const paths: TPathsD; isClosedPath: Boolean;
  142. tension: double = 0; shapeTolerance: double = 0.1): TPathsD;
  143. function GetFloodFillMask(imgIn, imgMaskOut: TImage32; x, y: Integer;
  144. tolerance: Byte; compareFunc: TCompareFunctionEx): Boolean;
  145. procedure SymmetricCropTransparent(img: TImage32);
  146. //3 additional blend functions (see TImage32.CopyBlend)
  147. function BlendAverage(bgColor, fgColor: TColor32): TColor32;
  148. function BlendLinearBurn(bgColor, fgColor: TColor32): TColor32;
  149. function BlendColorDodge(bgColor, fgColor: TColor32): TColor32;
  150. implementation
  151. uses
  152. {$IFDEF USING_FMX}
  153. Img32.FMX,
  154. {$ENDIF}
  155. Img32.Transform;
  156. const
  157. FloodFillDefaultRGBTolerance: byte = 64;
  158. MaxBlur = 100;
  159. type
  160. PColor32Array = ^TColor32Array;
  161. TColor32Array = array [0.. maxint div SizeOf(TColor32) -1] of TColor32;
  162. PWeightedColorArray = ^TWeightedColorArray;
  163. TWeightedColorArray = array [0.. $FFFFFF] of TWeightedColor;
  164. // SimplifyPathsEx structures
  165. PVertex = ^TVertex;
  166. TVertex = record
  167. pt : TPointD;
  168. uvec : TPointD;
  169. dist : double;
  170. perpD : double;
  171. next : PVertex;
  172. prev: PVertex;
  173. end;
  174. TArrayOfVertices = array of TVertex;
  175. //------------------------------------------------------------------------------
  176. // Miscellaneous functions
  177. //------------------------------------------------------------------------------
  178. function Clamp(val, endVal: integer): integer;
  179. {$IFDEF INLINE} inline; {$ENDIF}
  180. begin
  181. if val < 0 then Result := 0
  182. else if val >= endVal then Result := endVal -1
  183. else Result := val;
  184. end;
  185. //------------------------------------------------------------------------------
  186. function ModEx(val, endVal: integer): integer;
  187. {$IFDEF INLINE} inline; {$ENDIF}
  188. begin
  189. Result := val mod endVal;
  190. if Result < 0 then Result := endVal + Result;
  191. end;
  192. //------------------------------------------------------------------------------
  193. function GetSymmetricCropTransparentRect(img: TImage32): TRect;
  194. var
  195. w,h, x,y, x1,y1: Integer;
  196. p1,p2: PARGB;
  197. opaquePxlFound: Boolean;
  198. begin
  199. Result := img.Bounds;
  200. w := img.Width;
  201. y1 := 0;
  202. opaquePxlFound := false;
  203. for y := 0 to (img.Height div 2) -1 do
  204. begin
  205. p1 := PARGB(img.PixelRow[y]);
  206. p2 := PARGB(img.PixelRow[img.Height - y -1]);
  207. for x := 0 to w -1 do
  208. begin
  209. if (p1.A > 0) or (p2.A > 0) then
  210. begin
  211. y1 := y;
  212. opaquePxlFound := true;
  213. break;
  214. end;
  215. inc(p1); inc(p2);
  216. end;
  217. if opaquePxlFound then break;
  218. end;
  219. // probably safeset not to resize empty images
  220. if not opaquePxlFound then Exit;
  221. if y1 > 0 then
  222. begin
  223. inc(Result.Top, y1);
  224. dec(Result.Bottom, y1);
  225. end;
  226. x1 := 0;
  227. h := RectHeight(Result);
  228. opaquePxlFound := false;
  229. for x := 0 to (w div 2) -1 do
  230. begin
  231. p1 := PARGB(@img.Pixels[Result.Top * w + x]);
  232. p2 := PARGB(@img.Pixels[Result.Top * w + (w -1) - x]);
  233. for y := 0 to h -1 do
  234. begin
  235. if (p1.A > 0) or (p2.A > 0) then
  236. begin
  237. x1 := x;
  238. opaquePxlFound := true;
  239. break;
  240. end;
  241. inc(p1, w); inc(p2, w);
  242. end;
  243. if opaquePxlFound then break;
  244. end;
  245. if not opaquePxlFound then Exit;
  246. inc(Result.Left, x1);
  247. dec(Result.Right, x1);
  248. end;
  249. //------------------------------------------------------------------------------
  250. //SymmetricCropTransparent: after cropping, the image's midpoint
  251. //will be the same pixel as before cropping. (Important for rotating.)
  252. procedure SymmetricCropTransparent(img: TImage32);
  253. var
  254. rec: TRect;
  255. begin
  256. rec := GetSymmetricCropTransparentRect(img);
  257. if (rec.Top > 0) or (rec.Left > 0) then img.Crop(rec);
  258. end;
  259. //------------------------------------------------------------------------------
  260. procedure DrawEdge(img: TImage32; const rec: TRect;
  261. topLeftColor, bottomRightColor: TColor32; penWidth: double = 1.0);
  262. begin
  263. DrawEdge(img, RectD(rec), topLeftColor, bottomRightColor, penWidth);
  264. end;
  265. //------------------------------------------------------------------------------
  266. procedure DrawEdge(img: TImage32; const rec: TRectD;
  267. topLeftColor, bottomRightColor: TColor32; penWidth: double = 1.0);
  268. var
  269. p: TPathD;
  270. c: TColor32;
  271. begin
  272. if penWidth = 0 then Exit
  273. else if penWidth < 0 then
  274. begin
  275. c := topLeftColor;
  276. topLeftColor := bottomRightColor;
  277. bottomRightColor := c;
  278. penWidth := -penWidth;
  279. end;
  280. if topLeftColor <> bottomRightColor then
  281. begin
  282. with rec do
  283. begin
  284. p := Img32.Vector.MakePath([left, bottom, left, top, right, top]);
  285. DrawLine(img, p, penWidth, topLeftColor, esButt);
  286. p := Img32.Vector.MakePath([right, top, right, bottom, left, bottom]);
  287. DrawLine(img, p, penWidth, bottomRightColor, esButt);
  288. end;
  289. end else
  290. DrawLine(img, Rectangle(rec), penWidth, topLeftColor, esPolygon);
  291. end;
  292. //------------------------------------------------------------------------------
  293. procedure DrawEdge(img: TImage32; const path: TPathD;
  294. topLeftColor, bottomRightColor: TColor32;
  295. penWidth: double = 1.0; closePath: Boolean = true);
  296. var
  297. i, highI, deg: integer;
  298. frac: double;
  299. c: TColor32;
  300. p: TPathD;
  301. const
  302. RadToDeg = 180/PI;
  303. begin
  304. if penWidth = 0 then Exit
  305. else if penWidth < 0 then
  306. begin
  307. c := topLeftColor;
  308. topLeftColor := bottomRightColor;
  309. bottomRightColor := c;
  310. penWidth := -penWidth;
  311. end;
  312. highI := high(path);
  313. if highI < 2 then Exit;
  314. p := path;
  315. if closePath and not PointsNearEqual(p[0], p[highI], 0.01) then
  316. begin
  317. AppendPoint(p, p[0]);
  318. inc(highI);
  319. end;
  320. for i := 1 to highI do
  321. begin
  322. deg := Round(GetAngle(p[i-1], p[i]) * RadToDeg);
  323. case deg of
  324. -180..-136: frac := (-deg-135)/45;
  325. -135..0 : frac := 0;
  326. 1..44 : frac := deg/45;
  327. else frac := 1;
  328. end;
  329. c := GradientColor(topLeftColor, bottomRightColor, frac);
  330. DrawLine(img, p[i-1], p[i], penWidth, c);
  331. end;
  332. end;
  333. //------------------------------------------------------------------------------
  334. procedure FillColorHorz(img: TImage32; x, endX, y: integer; color: TColor32);
  335. var
  336. i,dx: integer;
  337. p: PColor32;
  338. begin
  339. if (x < 0) or (x >= img.Width) then Exit;
  340. if (y < 0) or (y >= img.Height) then Exit;
  341. p := img.PixelRow[y]; inc(p, x);
  342. if endX >= img.Width then endX := img.Width -1
  343. else if endX < 0 then endX := 0;
  344. if endX < x then dx := -1 else dx := 1;
  345. for i := 0 to Abs(x-endX) do
  346. begin
  347. p^ := color;
  348. inc(p, dx);
  349. end;
  350. end;
  351. //------------------------------------------------------------------------------
  352. procedure FillColorVert(img: TImage32; x, y, endY: integer; color: TColor32);
  353. var
  354. i, dy: integer;
  355. p: PColor32;
  356. begin
  357. if (x < 0) or (x >= img.Width) then Exit;
  358. if (y < 0) or (y >= img.Height) then Exit;
  359. p := img.PixelRow[y]; inc(p, x);
  360. if endY >= img.Height then
  361. endY := img.Height -1 else if endY < 0 then endY := 0;
  362. dy := img.Width;
  363. if endY < y then dy := -dy;
  364. for i := 0 to Abs(y - endY) do
  365. begin
  366. p^ := color;
  367. inc(p, dy);
  368. end;
  369. end;
  370. //------------------------------------------------------------------------------
  371. procedure DrawShadowRect(img: TImage32; const rec: TRect; depth: double;
  372. angle: double = angle45; color: TColor32 = $80000000);
  373. var
  374. i,j, sX,sY: integer;
  375. l,t,r,b: integer;
  376. tmpImg: TImage32;
  377. tmpRec: TRect;
  378. xx,yy: double;
  379. ss: TPointD;
  380. c: TColor32;
  381. begin
  382. GetSinCos(angle, yy, xx);
  383. ss.X := depth * xx;
  384. ss.Y := depth * yy;
  385. sX := Abs(Round(ss.X));
  386. sY := Abs(Round(ss.Y));
  387. if rec.Left + ss.X < 0 then ss.X := -rec.Left
  388. else if rec.Right + ss.X > img.Width then ss.X := img.Width - rec.Right -1;
  389. if rec.Top + ss.Y < 0 then ss.Y := -rec.Top
  390. else if rec.Bottom + ss.Y > img.Height then ss.Y := img.Height -rec.Bottom -1;
  391. tmpImg := TImage32.Create(sX*3 +1, sY*3 +1);
  392. try
  393. i := sX div 2; j := sY div 2;
  394. DrawPolygon(tmpImg, Rectangle(i,j,i+sX*2,j+sY*2), frNonZero, color);
  395. FastGaussianBlur(tmpImg, tmpImg.Bounds, Round(sX/4),Round(sY/4), 1);
  396. // t-l corner
  397. if (ss.X < 0) or (ss.Y < 0) then
  398. begin
  399. tmpRec := Rect(0, 0, sX, sY);
  400. l := rec.Left; t := rec.Top;
  401. if ss.X < 0 then dec(l, sX);
  402. if ss.Y < 0 then dec(t, sY);
  403. img.Copy(tmpImg, tmpRec, Rect(l,t,l+sX,t+sY));
  404. end;
  405. // t-r corner
  406. if (ss.X > 0) or (ss.Y < 0) then
  407. begin
  408. tmpRec := Rect(sX*2+1, 0, sX*3+1, sY);
  409. l := rec.Right; t := rec.Top;
  410. if ss.X < 0 then dec(l, sX);
  411. if ss.Y < 0 then dec(t, sY);
  412. img.Copy(tmpImg, tmpRec, Rect(l,t,l+sX,t+sY));
  413. end;
  414. // b-l corner
  415. if (ss.X < 0) or (ss.Y > 0) then
  416. begin
  417. tmpRec := Rect(0, sY*2+1, sX, sY*3+1);
  418. l := rec.Left; t := rec.Bottom;
  419. if ss.X < 0 then dec(l, sX);
  420. if ss.Y < 0 then dec(t, sY);
  421. img.Copy(tmpImg, tmpRec, Rect(l,t,l+sX,t+sY));
  422. end;
  423. // b-r corner
  424. if (ss.X > 0) or (ss.Y > 0) then
  425. begin
  426. tmpRec := Rect(sX*2+1, sY*2+1, sX*3+1, sY*3+1);
  427. l := rec.Right; t := rec.Bottom;
  428. if ss.X < 0 then dec(l, sX);
  429. if ss.Y < 0 then dec(t, sY);
  430. img.Copy(tmpImg, tmpRec, Rect(l,t,l+sX,t+sY));
  431. end;
  432. // l-edge
  433. if (ss.X < 0) then
  434. begin
  435. l := rec.Left; t := rec.Top+sY; b := rec.Bottom-1;
  436. if ss.Y < 0 then begin dec(t, sY); dec(b,sY); end;
  437. for i := 1 to sX do
  438. begin
  439. c := tmpImg.Pixel[sX-i, sY+1];
  440. FillColorVert(img, l-i, t, b, c);
  441. end;
  442. end;
  443. // t-edge
  444. if (ss.Y < 0) then
  445. begin
  446. l := rec.Left+sX; r := rec.Right-1; t := rec.Top;
  447. if ss.X < 0 then begin dec(l, sX); dec(r,sX); end;
  448. for i := 1 to sY do
  449. begin
  450. c := tmpImg.Pixel[sX+1, sY-i];
  451. FillColorHorz(img, l, r, t-i, c);
  452. end;
  453. end;
  454. // r-edge
  455. if (ss.X > 0) then
  456. begin
  457. r := rec.Right-1; t := rec.Top+sY; b := rec.Bottom-1;
  458. if ss.Y < 0 then begin dec(t, sY); dec(b,sY); end;
  459. for i := 1 to sX do
  460. begin
  461. c := tmpImg.Pixel[sX*2+i, sY+1];
  462. FillColorVert(img, r+i, t, b, c);
  463. end;
  464. end;
  465. // b-edge
  466. if (ss.Y > 0) then
  467. begin
  468. l := rec.Left+sX; r := rec.Right-1; b := rec.Bottom-1;
  469. if ss.X < 0 then begin dec(l, sX); dec(r,sX); end;
  470. for i := 1 to sY do
  471. begin
  472. c := tmpImg.Pixel[sX+1, sY*2+i];
  473. FillColorHorz(img, l, r, b+i, c);
  474. end;
  475. end;
  476. finally
  477. tmpImg.Free;
  478. end;
  479. end;
  480. //------------------------------------------------------------------------------
  481. procedure DrawShadow(img: TImage32; const polygon: TPathD;
  482. fillRule: TFillRule; depth: double; angleRads: double;
  483. color: TColor32; cutoutInsideShadow: Boolean);
  484. var
  485. polygons: TPathsD;
  486. begin
  487. setlength(polygons, 1);
  488. polygons[0] := polygon;
  489. DrawShadow(img, polygons, fillRule, depth,
  490. angleRads, color, cutoutInsideShadow);
  491. end;
  492. //------------------------------------------------------------------------------
  493. procedure DrawShadow(img: TImage32; const polygons: TPathsD;
  494. fillRule: TFillRule; depth: double; angleRads: double;
  495. color: TColor32; cutoutInsideShadow: Boolean);
  496. var
  497. x, y: double;
  498. blurSize, w,h: integer;
  499. rec: TRect;
  500. polys, shadowPolys: TPathsD;
  501. shadowImg: TImage32;
  502. begin
  503. rec := GetBounds(polygons);
  504. if IsEmptyRect(rec) or (depth < 1) then Exit;
  505. {$IFDEF CLOCKWISE_ROTATION_WITH_NEGATIVE_ANGLES}
  506. angleRads := -angleRads;
  507. {$ENDIF}
  508. NormalizeAngle(angleRads);
  509. GetSinCos(angleRads, y, x);
  510. depth := depth * 0.5;
  511. x := depth * x;
  512. y := depth * y;
  513. blurSize := Max(1,Round(depth / 2));
  514. Img32.Vector.InflateRect(rec, Ceil(depth*2), Ceil(depth*2));
  515. polys := TranslatePath(polygons, -rec.Left, -rec.Top);
  516. shadowPolys := TranslatePath(polys, x, y);
  517. RectWidthHeight(rec, w, h);
  518. shadowImg := TImage32.Create(w, h);
  519. try
  520. DrawPolygon(shadowImg, shadowPolys, fillRule, color);
  521. FastGaussianBlur(shadowImg, shadowImg.Bounds, blurSize, 1);
  522. if cutoutInsideShadow then EraseInsidePaths(shadowImg, polys, fillRule);
  523. img.CopyBlend(shadowImg, shadowImg.Bounds, rec, BlendToAlphaLine);
  524. finally
  525. shadowImg.Free;
  526. end;
  527. end;
  528. //------------------------------------------------------------------------------
  529. procedure DrawGlow(img: TImage32; const polygon: TPathD;
  530. fillRule: TFillRule; color: TColor32; blurRadius: integer);
  531. var
  532. polygons: TPathsD;
  533. begin
  534. setlength(polygons, 1);
  535. polygons[0] := polygon;
  536. DrawGlow(img, polygons, fillRule, color, blurRadius);
  537. end;
  538. //------------------------------------------------------------------------------
  539. procedure DrawGlow(img: TImage32; const polygons: TPathsD;
  540. fillRule: TFillRule; color: TColor32; blurRadius: integer);
  541. var
  542. w,h: integer;
  543. rec: TRect;
  544. glowPolys: TPathsD;
  545. glowImg: TImage32;
  546. begin
  547. rec := GetBounds(polygons);
  548. glowPolys := TranslatePath(polygons,
  549. blurRadius -rec.Left +1, blurRadius -rec.Top +1);
  550. Img32.Vector.InflateRect(rec, blurRadius +1, blurRadius +1);
  551. RectWidthHeight(rec, w, h);
  552. glowImg := TImage32.Create(w, h);
  553. try
  554. DrawPolygon(glowImg, glowPolys, fillRule, color);
  555. FastGaussianBlur(glowImg, glowImg.Bounds, blurRadius, 2);
  556. glowImg.ScaleAlpha(4);
  557. img.CopyBlend(glowImg, glowImg.Bounds, rec, BlendToAlphaLine);
  558. finally
  559. glowImg.Free;
  560. end;
  561. end;
  562. //------------------------------------------------------------------------------
  563. procedure Sharpen(img: TImage32; radius: Integer; amount: Integer);
  564. var
  565. i: Integer;
  566. amt: double;
  567. weightAmount: array [-255 .. 255] of Integer;
  568. bmpBlur: TImage32;
  569. pColor, pBlur: PARGB;
  570. begin
  571. if radius = 0 then Exit;
  572. amt := ClampRange(amount/10, 0.1, 5);
  573. radius := ClampRange(radius, 1, 10);
  574. for i := -255 to 255 do
  575. weightAmount[i] := Round(amt * i);
  576. bmpBlur := TImage32.Create(img); // clone self
  577. try
  578. pColor := PARGB(img.pixelBase);
  579. FastGaussianBlur(bmpBlur, bmpBlur.Bounds, radius, 2);
  580. pBlur := PARGB(bmpBlur.pixelBase);
  581. for i := 1 to img.Width * img.Height do
  582. begin
  583. if (pColor.A > 0) then
  584. begin
  585. pColor.R := ClampByte(pColor.R + weightAmount[pColor.R - pBlur.R]);
  586. pColor.G := ClampByte(pColor.G + weightAmount[pColor.G - pBlur.G]);
  587. pColor.B := ClampByte(pColor.B + weightAmount[pColor.B - pBlur.B]);
  588. end;
  589. Inc(pColor); Inc(pBlur);
  590. end;
  591. finally
  592. bmpBlur.Free;
  593. end;
  594. end;
  595. //------------------------------------------------------------------------------
  596. procedure InternalHatchBackground(img: TImage32; const rec: TRect;
  597. color1, color2: TColor32; hatchSize: Integer = 10);
  598. var
  599. i, j, imgWidth: Integer;
  600. pc: PColor32;
  601. colors: array[boolean] of TColor32;
  602. hatch: Boolean;
  603. x: integer;
  604. begin
  605. colors[false] := color1;
  606. colors[true] := color2;
  607. imgWidth := img.Width;
  608. for i := rec.Top to rec.Bottom -1 do
  609. begin
  610. pc := @img.Pixels[i * imgWidth + rec.Left];
  611. hatch := Odd(i div hatchSize);
  612. x := (rec.Left + 1) mod hatchSize;
  613. if x = 0 then hatch := not hatch;
  614. for j := rec.Left to rec.Right -1 do
  615. begin
  616. if pc^ = 0 then
  617. pc^ := colors[hatch]
  618. else if GetAlpha(pc^) < 255 then
  619. pc^ := BlendToOpaque(colors[hatch], pc^);
  620. inc(pc);
  621. inc(x);
  622. if x >= hatchSize then
  623. begin
  624. x := 0;
  625. hatch := not hatch;
  626. end;
  627. end;
  628. end;
  629. end;
  630. //------------------------------------------------------------------------------
  631. procedure HatchBackground(img: TImage32; const rec: TRect;
  632. color1: TColor32 = clWhite32; color2: TColor32= $FFE8E8E8;
  633. hatchSize: Integer = 10); overload;
  634. begin
  635. if (rec.Right <= rec.Left) or (rec.Bottom - rec.Top <= 0) then Exit;
  636. img.BeginUpdate;
  637. try
  638. InternalHatchBackground(img, rec, color1, color2, hatchSize);
  639. finally
  640. img.EndUpdate;
  641. end;
  642. end;
  643. //------------------------------------------------------------------------------
  644. procedure HatchBackground(img: TImage32;
  645. color1: TColor32; color2: TColor32; hatchSize: Integer);
  646. begin
  647. HatchBackground(img, img.Bounds, color1, color2, hatchSize);
  648. end;
  649. //------------------------------------------------------------------------------
  650. procedure GridBackground(img: TImage32; majorInterval, minorInterval: integer;
  651. fillColor: TColor32; majColor: TColor32; minColor: TColor32);
  652. var
  653. i, x,y, w,h: integer;
  654. path: TPathD;
  655. cr: TCustomColorRenderer;
  656. begin
  657. img.Clear(fillColor);
  658. w := img.Width; h := img.Height;
  659. NewPointDArray(path, 2, True);
  660. if img.AntiAliased then
  661. cr := TColorRenderer.Create(minColor) else
  662. cr := TAliasedColorRenderer.Create(minColor);
  663. try
  664. if minorInterval > 0 then
  665. begin
  666. //cr.SetColor(minColor);
  667. x := minorInterval;
  668. path[0] := PointD(x, 0); path[1] := PointD(x, h);;
  669. for i := 1 to (w div minorInterval) do
  670. begin
  671. Img32.Draw.DrawLine(img, path, 1, cr, esSquare);
  672. path[0].X := path[0].X + minorInterval;
  673. path[1].X := path[1].X + minorInterval;
  674. end;
  675. y := minorInterval;
  676. path[0] := PointD(0, y); path[1] := PointD(w, y);
  677. for i := 1 to (h div minorInterval) do
  678. begin
  679. Img32.Draw.DrawLine(img, path, 1, cr, esSquare);
  680. path[0].Y := path[0].Y + minorInterval;
  681. path[1].Y := path[1].Y + minorInterval;
  682. end;
  683. end;
  684. if majorInterval > minorInterval then
  685. begin
  686. cr.SetColor(majColor);
  687. x := majorInterval;
  688. path[0] := PointD(x, 0); path[1] := PointD(x, h);;
  689. for i := 1 to (w div majorInterval) do
  690. begin
  691. Img32.Draw.DrawLine(img, path, 1, cr, esSquare);
  692. path[0].X := path[0].X + majorInterval;
  693. path[1].X := path[1].X + majorInterval;
  694. end;
  695. y := majorInterval;
  696. path[0] := PointD(0, y); path[1] := PointD(w, y);
  697. for i := 1 to (h div majorInterval) do
  698. begin
  699. Img32.Draw.DrawLine(img, path, 1, cr, esSquare);
  700. path[0].Y := path[0].Y + majorInterval;
  701. path[1].Y := path[1].Y + majorInterval;
  702. end;
  703. end;
  704. finally
  705. cr.Free;
  706. end;
  707. end;
  708. //------------------------------------------------------------------------------
  709. function ColorDifference(color1, color2: TColor32): cardinal;
  710. {$IFDEF INLINE} inline; {$ENDIF}
  711. var
  712. c1: TARGB absolute color1;
  713. c2: TARGB absolute color2;
  714. begin
  715. result := Abs(c1.R - c2.R) + Abs(c1.G - c2.G) + Abs(c1.B - c2.B);
  716. result := (result * 341) shr 10; // divide by 3
  717. end;
  718. //------------------------------------------------------------------------------
  719. procedure ReplaceExactColor(img: TImage32; oldColor, newColor: TColor32);
  720. var
  721. color: PColor32;
  722. i: Integer;
  723. begin
  724. color := img.PixelBase;
  725. for i := 0 to img.Width * img.Height -1 do
  726. begin
  727. if color^ = oldColor then color^ := newColor;
  728. inc(color);
  729. end;
  730. end;
  731. //------------------------------------------------------------------------------
  732. procedure RemoveColor(img: TImage32; color: TColor32);
  733. var
  734. fg: TARGB absolute color;
  735. bg: PARGB;
  736. i: Integer;
  737. Q: byte;
  738. begin
  739. if fg.A = 0 then Exit;
  740. bg := PARGB(img.PixelBase);
  741. for i := 0 to img.Width * img.Height -1 do
  742. begin
  743. if bg.A > 0 then
  744. begin
  745. // red
  746. if (bg.R > fg.R) then Q := bg.R - fg.R
  747. else if (bg.R < fg.R) then Q := DivTable[fg.R - bg.R, fg.R]
  748. else Q := 0;
  749. // green
  750. if (bg.G > fg.G) then Q := Max(Q, bg.G - fg.G)
  751. else if (bg.G < fg.G) then Q := Max(Q, DivTable[fg.G - bg.G, fg.G]);
  752. // blue
  753. if (bg.B > fg.B) then Q := Max(Q, bg.B - fg.B)
  754. else if (bg.B < fg.B) then Q := Max(Q, DivTable[fg.B - bg.B, fg.B]);
  755. // weight Q toward either fully opaque or fully translucent
  756. Q := Sigmoid[Q];
  757. if (Q = 0) then
  758. bg.Color := clNone32
  759. else if (Q < 255) then
  760. begin
  761. bg.A := MulTable[bg.A, Q];
  762. bg.R := DivTable[bg.R - MulTable[not Q, fg.R], Q];
  763. bg.G := DivTable[bg.G - MulTable[not Q, fg.G], Q];
  764. bg.B := DivTable[bg.B - MulTable[not Q, fg.B], Q];
  765. end;
  766. end;
  767. inc(bg);
  768. end;
  769. end;
  770. //------------------------------------------------------------------------------
  771. procedure FilterOnColor(img: TImage32; color: TColor32);
  772. var
  773. fg: TARGB absolute color;
  774. bg: PARGB;
  775. i: Integer;
  776. Q: byte;
  777. begin
  778. if fg.A = 0 then Exit;
  779. bg := PARGB(img.PixelBase);
  780. for i := 0 to img.Width * img.Height -1 do
  781. begin
  782. if bg.A > 0 then
  783. begin
  784. // red
  785. if (bg.R > fg.R) then
  786. Q := bg.R - fg.R
  787. else if (bg.R < fg.R) then
  788. Q := DivTable[fg.R - bg.R, fg.R]
  789. else
  790. Q := 0;
  791. // green
  792. if (bg.G > fg.G) then
  793. Q := Max(Q, bg.G - fg.G)
  794. else if (bg.G < fg.G) then
  795. Q := Max(Q, DivTable[fg.G - bg.G, fg.G]);
  796. // blue
  797. if (bg.B > fg.B) then
  798. Q := Max(Q, bg.B - fg.B)
  799. else if (bg.B < fg.B) then
  800. Q := Max(Q, DivTable[fg.B - bg.B, fg.B]);
  801. // weight Q toward either fully opaque or fully translucent
  802. Q := Sigmoid[Q];
  803. Q := MulTable[bg.A, not Q];
  804. bg.Color := color;
  805. bg.A := Q; // note: fg.A is ignored
  806. end;
  807. inc(bg);
  808. end;
  809. end;
  810. //------------------------------------------------------------------------------
  811. procedure FilterOnExactColor(img: TImage32; color: TColor32);
  812. var
  813. pc: PColor32;
  814. i: Integer;
  815. mask: TColor32;
  816. begin
  817. // alpha channel is ignored
  818. mask := $FFFFFF;
  819. color := color and mask;
  820. pc := img.PixelBase;
  821. for i := 0 to img.Width * img.Height -1 do
  822. begin
  823. if (pc^ and mask) <> color then pc^ := clNone32;
  824. inc(pc);
  825. end;
  826. end;
  827. //------------------------------------------------------------------------------
  828. procedure FilterOnAlpha(img: TImage32; alpha: byte; tolerance: byte);
  829. var
  830. bg: PARGB;
  831. i: Integer;
  832. begin
  833. bg := PARGB(img.PixelBase);
  834. for i := 0 to img.Width * img.Height -1 do
  835. begin
  836. if abs(bg.A - alpha) > tolerance then bg.A := 0;
  837. inc(bg);
  838. end;
  839. end;
  840. //------------------------------------------------------------------------------
  841. procedure RedEyeRemove(img: TImage32; const rect: TRect);
  842. var
  843. k: integer;
  844. cutout, mask: TImage32;
  845. path: TPathD;
  846. cutoutRec, rect3: TRect;
  847. radGrad: TRadialGradientRenderer;
  848. begin
  849. k := RectWidth(rect) * RectHeight(rect);
  850. if k < 120 then k := 2
  851. else if k < 230 then k := 3
  852. else k := 4;
  853. cutoutRec := rect;
  854. Img32.Vector.InflateRect(cutoutRec, k, k);
  855. cutout := TImage32.Create(img, cutoutRec);
  856. mask := TImage32.Create(cutout.Width, cutout.Height);
  857. radGrad := TRadialGradientRenderer.Create;
  858. try
  859. // fill behind the cutout with black also
  860. // blurring the fill to soften its edges
  861. rect3 := cutout.Bounds;
  862. Img32.Vector.InflateRect(rect3, -k, -k);
  863. path := Ellipse(rect3);
  864. DrawPolygon(mask, path, frNonZero, clBlack32);
  865. // given the very small area and small radius of the blur, the
  866. // speed improvement of BoxBlur over GaussianBlur is inconsequential.
  867. GaussianBlur(mask, mask.Bounds, k);
  868. img.CopyBlend(mask, mask.Bounds, cutoutRec, BlendToOpaque);
  869. // gradient fill to clNone32 a mask to soften cutout's edges
  870. path := Ellipse(cutoutRec);
  871. radGrad.SetParameters(rect3, clBlack32, clNone32);
  872. DrawPolygon(mask, path, frNonZero, radGrad);
  873. cutout.CopyBlend(mask, mask.Bounds, cutout.Bounds, BlendMaskLine);
  874. // now remove red from the cutout
  875. RemoveColor(cutout, clRed32);
  876. // finally replace the cutout ...
  877. img.CopyBlend(cutout, cutout.Bounds, cutoutRec, BlendToOpaque);
  878. finally
  879. mask.Free;
  880. cutout.Free;
  881. radGrad.Free;
  882. end;
  883. end;
  884. //------------------------------------------------------------------------------
  885. procedure EraseInsidePath(img: TImage32; const path: TPathD; fillRule: TFillRule);
  886. begin
  887. if assigned(path) then
  888. ErasePolygon(img, path, fillRule);
  889. end;
  890. //------------------------------------------------------------------------------
  891. procedure EraseInsidePaths(img: TImage32; const paths: TPathsD; fillRule: TFillRule);
  892. begin
  893. if assigned(paths) then
  894. ErasePolygon(img, paths, fillRule);
  895. end;
  896. //------------------------------------------------------------------------------
  897. procedure EraseOutsideRect(img: TImage32; const r, outsideBounds: TRect);
  898. begin
  899. // Fill the parts, that are in outsideBounds but not in r with zeros
  900. // whole top block
  901. if r.Top > outsideBounds.Top then
  902. img.FillRect(Rect(outsideBounds.Left, outsideBounds.Top, outsideBounds.Right, r.Top - 1), 0);
  903. // whole bottom block
  904. if r.Bottom < outsideBounds.Bottom then
  905. img.FillRect(Rect(outsideBounds.Left, r.Bottom + 1, outsideBounds.Right, outsideBounds.Bottom), 0);
  906. // remaining left block
  907. if r.Left > outsideBounds.Left then
  908. img.FillRect(Rect(outsideBounds.Left, r.Top, r.Left - 1, r.Bottom), 0);
  909. // remaining right block
  910. if r.Right < outsideBounds.Right then
  911. img.FillRect(Rect(r.Right + 1, r.Top, outsideBounds.Right, r.Bottom), 0);
  912. end;
  913. //------------------------------------------------------------------------------
  914. procedure EraseOutsidePath(img: TImage32; const path: TPathD;
  915. fillRule: TFillRule; const outsideBounds: TRect);
  916. var
  917. w, h: integer;
  918. renderer: TMaskRenderer;
  919. r: TRect;
  920. polygons: TPathsD;
  921. begin
  922. if not assigned(path) then Exit;
  923. RectWidthHeight(outsideBounds, w, h);
  924. if (w <= 0) or (h <= 0) then Exit;
  925. // We can skip the costly polygon rasterization if the path is
  926. // a rectangle
  927. if (fillRule in [frEvenOdd, frNonZero]) and IsSimpleRectanglePath(path, r) then
  928. begin
  929. EraseOutsideRect(img, r, outsideBounds);
  930. Exit;
  931. end;
  932. renderer := TMaskRenderer.Create;
  933. try
  934. SetLength(polygons, 1);
  935. polygons[0] := path;
  936. Rasterize(img, polygons, outsideBounds, fillRule, renderer);
  937. finally
  938. renderer.Free;
  939. end;
  940. end;
  941. //------------------------------------------------------------------------------
  942. procedure EraseOutsidePaths(img: TImage32; const paths: TPathsD;
  943. fillRule: TFillRule; const outsideBounds: TRect;
  944. rendererCache: TCustomRendererCache);
  945. var
  946. w, h: integer;
  947. renderer: TMaskRenderer;
  948. r: TRect;
  949. begin
  950. if not assigned(paths) then Exit;
  951. RectWidthHeight(outsideBounds, w, h);
  952. if (w <= 0) or (h <= 0) then Exit;
  953. // We can skip the costly polygon rasterization if the path is
  954. // a rectangle.
  955. if (fillRule in [frEvenOdd, frNonZero]) and IsSimpleRectanglePath(paths, r) then
  956. begin
  957. EraseOutsideRect(img, r, outsideBounds);
  958. Exit;
  959. end;
  960. if rendererCache = nil then
  961. renderer := TMaskRenderer.Create
  962. else
  963. renderer := rendererCache.MaskRenderer;
  964. try
  965. Rasterize(img, paths, outsideBounds, fillRule, renderer);
  966. finally
  967. if rendererCache = nil then
  968. renderer.Free;
  969. end;
  970. end;
  971. //------------------------------------------------------------------------------
  972. procedure Draw3D(img: TImage32; const polygon: TPathD;
  973. fillRule: TFillRule; height, blurRadius: double;
  974. colorLt: TColor32; colorDk: TColor32; angleRads: double);
  975. var
  976. polygons: TPathsD;
  977. begin
  978. setLength(polygons, 1);
  979. polygons[0] := polygon;
  980. Draw3D(img, polygons, fillRule, height, blurRadius, colorLt, colorDk, angleRads);
  981. end;
  982. //------------------------------------------------------------------------------
  983. procedure Draw3D(img: TImage32; const polygons: TPathsD;
  984. fillRule: TFillRule; height, blurRadius: double;
  985. colorLt: TColor32; colorDk: TColor32; angleRads: double);
  986. var
  987. tmp: TImage32;
  988. rec: TRect;
  989. paths, paths2: TPathsD;
  990. w,h: integer;
  991. x,y: double;
  992. begin
  993. rec := GetBounds(polygons);
  994. if IsEmptyRect(rec) then Exit;
  995. {$IFDEF CLOCKWISE_ROTATION_WITH_NEGATIVE_ANGLES}
  996. angleRads := -angleRads;
  997. {$ENDIF}
  998. GetSinCos(angleRads, y, x);
  999. paths := TranslatePath(polygons, -rec.Left, -rec.Top);
  1000. RectWidthHeight(rec, w, h);
  1001. tmp := TImage32.Create(w, h);
  1002. try
  1003. if GetAlpha(colorLt) > 0 then
  1004. begin
  1005. tmp.Clear(colorLt);
  1006. paths2 := TranslatePath(paths, -height*x, -height*y);
  1007. EraseInsidePaths(tmp, paths2, fillRule);
  1008. FastGaussianBlur(tmp, tmp.Bounds, Round(blurRadius), 0);
  1009. EraseOutsidePaths(tmp, paths, fillRule, tmp.Bounds);
  1010. img.CopyBlend(tmp, tmp.Bounds, rec, BlendToAlphaLine);
  1011. end;
  1012. if GetAlpha(colorDk) > 0 then
  1013. begin
  1014. tmp.Clear(colorDk);
  1015. paths2 := TranslatePath(paths, height*x, height*y);
  1016. EraseInsidePaths(tmp, paths2, fillRule);
  1017. FastGaussianBlur(tmp, tmp.Bounds, Round(blurRadius), 0);
  1018. EraseOutsidePaths(tmp, paths, fillRule, tmp.Bounds);
  1019. img.CopyBlend(tmp, tmp.Bounds, rec, BlendToAlphaLine);
  1020. end;
  1021. finally
  1022. tmp.Free;
  1023. end;
  1024. end;
  1025. //------------------------------------------------------------------------------
  1026. function RainbowColor(fraction: double; luminance: byte = 128): TColor32;
  1027. var
  1028. hsl: THsl;
  1029. begin
  1030. if (fraction < 0) or (fraction > 1) then
  1031. fraction := frac(fraction);
  1032. hsl.hue := Round(fraction * 255);
  1033. hsl.sat := 255;
  1034. hsl.lum := luminance;
  1035. hsl.alpha := 255;
  1036. Result := HslToRgb(hsl);
  1037. end;
  1038. //------------------------------------------------------------------------------
  1039. function GradientColor(color1, color2: TColor32; frac: single): TColor32;
  1040. var
  1041. hsl1, hsl2: THsl;
  1042. begin
  1043. if (frac <= 0) then result := color1
  1044. else if (frac >= 1) then result := color2
  1045. else
  1046. begin
  1047. hsl1 := RgbToHsl(color1); hsl2 := RgbToHsl(color2);
  1048. hsl1.hue := ClampByte(hsl1.hue*(1-frac) + hsl2.hue*frac);
  1049. hsl1.sat := ClampByte(hsl1.sat*(1-frac) + hsl2.sat*frac);
  1050. hsl1.lum := ClampByte(hsl1.lum*(1-frac) + hsl2.lum*frac);
  1051. hsl1.alpha := ClampByte(hsl1.alpha*(1-frac) + hsl2.alpha*frac);
  1052. Result := HslToRgb(hsl1);
  1053. end;
  1054. end;
  1055. //------------------------------------------------------------------------------
  1056. function MakeDarker(color: TColor32; percent: cardinal): TColor32;
  1057. var
  1058. hsl: THsl;
  1059. begin
  1060. hsl := RgbToHsl(color);
  1061. hsl.lum := ClampByte(hsl.lum - (percent/100 * hsl.lum));
  1062. Result := HslToRgb(hsl);
  1063. end;
  1064. //------------------------------------------------------------------------------
  1065. function MakeLighter(color: TColor32; percent: cardinal): TColor32;
  1066. var
  1067. hsl: THsl;
  1068. begin
  1069. hsl := RgbToHsl(color);
  1070. hsl.lum := ClampByte(hsl.lum + percent/100 * (255 - hsl.lum));
  1071. Result := HslToRgb(hsl);
  1072. end;
  1073. //------------------------------------------------------------------------------
  1074. function DrawButton(img: TImage32; const pt: TPointD;
  1075. size: double; color: TColor32; buttonShape: TButtonShape;
  1076. buttonAttributes: TButtonAttributes): TPathD;
  1077. var
  1078. i: integer;
  1079. radius: double;
  1080. rec: TRectD;
  1081. lightSize, lightAngle: double;
  1082. begin
  1083. if (size < 5) then Exit;
  1084. radius := size * 0.5;
  1085. lightSize := radius * 0.25;
  1086. rec := RectD(pt.X -radius, pt.Y -radius, pt.X +radius, pt.Y +radius);
  1087. if baEraseBeneath in buttonAttributes then
  1088. img.Clear(Rect(rec));
  1089. case buttonShape of
  1090. bsDiamond:
  1091. begin
  1092. NewPointDArray(Result, 4, True);
  1093. for i := 0 to 3 do Result[i] := pt;
  1094. Result[0].X := Result[0].X -radius;
  1095. Result[1].Y := Result[1].Y -radius;
  1096. Result[2].X := Result[2].X +radius;
  1097. Result[3].Y := Result[3].Y +radius;
  1098. end;
  1099. bsSquare:
  1100. begin
  1101. Img32.Vector.InflateRect(rec, -1,-1);
  1102. Result := Rectangle(rec);
  1103. end;
  1104. else
  1105. Result := Ellipse(rec);
  1106. end;
  1107. lightAngle := angle225;
  1108. img.BeginUpdate;
  1109. try
  1110. // nb: only need to cutout the inside shadow if
  1111. // the pending color fill is semi-transparent
  1112. if baShadow in buttonAttributes then
  1113. DrawShadow(img, Result, frNonZero, lightSize *2,
  1114. (lightAngle + angle180), $AA000000, GetAlpha(color) < $FE);
  1115. if GetAlpha(color) > 2 then
  1116. DrawPolygon(img, Result, frNonZero, color);
  1117. if ba3D in buttonAttributes then
  1118. Draw3D(img, Result, frNonZero, lightSize*2,
  1119. Ceil(lightSize), $CCFFFFFF, $AA000000, lightAngle);
  1120. DrawLine(img, Result, dpiAware1, clBlack32, esPolygon);
  1121. finally
  1122. img.EndUpdate;
  1123. end;
  1124. end;
  1125. //------------------------------------------------------------------------------
  1126. function AlphaAverage(color1, color2: TColor32): cardinal;
  1127. {$IFDEF INLINE} inline; {$ENDIF}
  1128. var
  1129. c1: TARGB absolute color1;
  1130. c2: TARGB absolute color2;
  1131. begin
  1132. result := (c1.A + c2.A) shr 1;
  1133. end;
  1134. //------------------------------------------------------------------------------
  1135. function BlendAverage(bgColor, fgColor: TColor32): TColor32;
  1136. var
  1137. res: TARGB absolute Result;
  1138. bg: TARGB absolute bgColor;
  1139. fg: TARGB absolute fgColor;
  1140. begin
  1141. res.A := (fg.A + bg.A) shr 1;
  1142. res.R := (fg.R + bg.R) shr 1;
  1143. res.G := (fg.G + bg.G) shr 1;
  1144. res.B := (fg.B + bg.B) shr 1;
  1145. end;
  1146. //------------------------------------------------------------------------------
  1147. function BlendLinearBurn(bgColor, fgColor: TColor32): TColor32;
  1148. var
  1149. res: TARGB absolute Result;
  1150. bg: TARGB absolute bgColor;
  1151. fg: TARGB absolute fgColor;
  1152. begin
  1153. res.A := 255;
  1154. res.R := Max(0, bg.R + fg.R - 255);
  1155. res.G := Max(0, bg.G + fg.G - 255);
  1156. res.B := Max(0, bg.B + fg.B - 255);
  1157. end;
  1158. //------------------------------------------------------------------------------
  1159. function BlendColorDodge(bgColor, fgColor: TColor32): TColor32;
  1160. var
  1161. res: TARGB absolute Result;
  1162. bg: TARGB absolute bgColor;
  1163. fg: TARGB absolute fgColor;
  1164. begin
  1165. res.A := 255;
  1166. res.R := DivTable[bg.R, not fg.R];
  1167. res.G := DivTable[bg.G, not fg.G];
  1168. res.B := DivTable[bg.B, not fg.B];
  1169. end;
  1170. //------------------------------------------------------------------------------
  1171. procedure PencilEffect(img: TImage32; intensity: integer);
  1172. var
  1173. img2: TImage32;
  1174. begin
  1175. if img.IsEmpty then Exit;
  1176. intensity := max(1, min(10, intensity));
  1177. img.Grayscale;
  1178. img2 := TImage32.Create(img);
  1179. try
  1180. img2.InvertColors;
  1181. FastGaussianBlur(img2, img2.Bounds, intensity, 2);
  1182. img.CopyBlend(img2, img2.Bounds, img.Bounds, BlendColorDodge);
  1183. finally
  1184. img2.Free;
  1185. end;
  1186. end;
  1187. //------------------------------------------------------------------------------
  1188. procedure TraceContours(img: TImage32; intensity: integer);
  1189. var
  1190. i,j, w,h: integer;
  1191. tmp, tmp2: TArrayOfColor32;
  1192. s, s2: PColor32;
  1193. d: PARGB;
  1194. begin
  1195. w := img.Width; h := img.Height;
  1196. if w * h = 0 then Exit;
  1197. NewColor32Array(tmp, w * h);
  1198. NewColor32Array(tmp2, w * h);
  1199. s := img.PixelRow[0]; d := @tmp[0];
  1200. for j := 0 to h-1 do
  1201. begin
  1202. s2 := IncPColor32(s, 1);
  1203. for i := 0 to w-2 do
  1204. begin
  1205. d.A := ColorDifference(s^, s2^);
  1206. inc(s); inc(s2); inc(d);
  1207. end;
  1208. inc(s); inc(d);
  1209. end;
  1210. for j := 0 to w-1 do
  1211. begin
  1212. s := @tmp[j]; d := @tmp2[j];
  1213. s2 := IncPColor32(s, w);
  1214. for i := 0 to h-2 do
  1215. begin
  1216. d.A := AlphaAverage(s^, s2^);
  1217. inc(s, w); inc(s2, w); inc(d, w);
  1218. end;
  1219. end;
  1220. img.BlockNotify;
  1221. img.AssignPixelArray(tmp2, w, h);
  1222. img.UnblockNotify;
  1223. if intensity < 1 then Exit;
  1224. if intensity > 10 then
  1225. intensity := 10; // range = 1-10
  1226. img.ScaleAlpha(intensity);
  1227. end;
  1228. //------------------------------------------------------------------------------
  1229. // FLOODFILL - AND SUPPORT FUNCTIONS
  1230. //------------------------------------------------------------------------------
  1231. type
  1232. PFloodFillRec = ^TFloodFillRec;
  1233. TFloodFillRec = record
  1234. xLeft : Integer;
  1235. xRight : Integer;
  1236. y : Integer;
  1237. dirY : Integer;
  1238. next : PFloodFillRec;
  1239. end;
  1240. TFloodFillStack = class
  1241. first : PFloodFillRec;
  1242. maxY : integer;
  1243. constructor Create(maxY: integer);
  1244. destructor Destroy; override;
  1245. procedure Push(xLeft, xRight,y, direction: Integer);
  1246. procedure Pop(out xLeft, xRight,y, direction: Integer);
  1247. function IsEmpty: Boolean;
  1248. end;
  1249. TFloodFillMask = class
  1250. private
  1251. img : TImage32;
  1252. mask : TImage32;
  1253. colorsRow : PColor32Array;
  1254. maskRow : PColor32Array;
  1255. initialColor : TColor32;
  1256. compareFunc : TCompareFunctionEx;
  1257. tolerance : Integer;
  1258. public
  1259. function Execute(imgIn, imgMaskOut: TImage32; x,y: integer;
  1260. aTolerance: Byte = 0; compFunc: TCompareFunctionEx = nil): Boolean;
  1261. procedure SetCurrentY(y: Integer);
  1262. function IsMatch(x: Integer): Boolean;
  1263. end;
  1264. //------------------------------------------------------------------------------
  1265. // TFloodFillStack methods
  1266. //------------------------------------------------------------------------------
  1267. constructor TFloodFillStack.Create(maxY: integer);
  1268. begin
  1269. self.maxY := maxY;
  1270. end;
  1271. //------------------------------------------------------------------------------
  1272. destructor TFloodFillStack.Destroy;
  1273. var
  1274. ffr: PFloodFillRec;
  1275. begin
  1276. while assigned(first) do
  1277. begin
  1278. ffr := first;
  1279. first := first.next;
  1280. dispose(ffr);
  1281. end;
  1282. end;
  1283. //------------------------------------------------------------------------------
  1284. procedure TFloodFillStack.Push(xLeft, xRight, y, direction: Integer);
  1285. var
  1286. ffr: PFloodFillRec;
  1287. begin
  1288. if ((y <= 0) and (direction = -1)) or
  1289. ((y >= maxY) and (direction = 1)) then Exit;
  1290. new(ffr);
  1291. ffr.xLeft := xLeft;
  1292. ffr.xRight := xRight;
  1293. ffr.y := y;
  1294. ffr.dirY := direction;
  1295. ffr.next := first;
  1296. first := ffr;
  1297. end;
  1298. //------------------------------------------------------------------------------
  1299. procedure TFloodFillStack.Pop(out xLeft, xRight, y, direction: Integer);
  1300. var
  1301. ffr: PFloodFillRec;
  1302. begin
  1303. xLeft := first.xLeft;
  1304. xRight := first.xRight;
  1305. direction := first.dirY;
  1306. y := first.y + direction;
  1307. ffr := first;
  1308. first := first.next;
  1309. dispose(ffr);
  1310. end;
  1311. //------------------------------------------------------------------------------
  1312. function TFloodFillStack.IsEmpty: Boolean;
  1313. begin
  1314. result := not assigned(first);
  1315. end;
  1316. //------------------------------------------------------------------------------
  1317. // TFloodFillMask methods
  1318. //------------------------------------------------------------------------------
  1319. function TFloodFillMask.Execute(imgIn, imgMaskOut: TImage32; x,y: integer;
  1320. aTolerance: Byte; compFunc: TCompareFunctionEx): Boolean;
  1321. var
  1322. ffs : TFloodFillStack;
  1323. w,h : integer;
  1324. xl, xr, xr2 : Integer;
  1325. maxX : Integer;
  1326. dirY : Integer;
  1327. begin
  1328. Result := Assigned(imgIn) and Assigned(imgMaskOut) and
  1329. InRange(x,0,imgIn.Width -1) and InRange(y,0,imgIn.Height -1);
  1330. if not Result then Exit;
  1331. w := imgIn.Width; h := imgIn.Height;
  1332. // make sure the mask is the size of the image
  1333. imgMaskOut.SetSize(w,h);
  1334. img := imgIn;
  1335. mask := imgMaskOut;
  1336. compareFunc := compFunc;
  1337. tolerance := aTolerance;
  1338. maxX := w -1;
  1339. ffs := TFloodFillStack.create(h -1);
  1340. try
  1341. initialColor := imgIn.Pixel[x, y];
  1342. xl := x; xr := x;
  1343. SetCurrentY(y);
  1344. IsMatch(x);
  1345. while (xl > 0) and IsMatch(xl -1) do dec(xl);
  1346. while (xr < maxX) and IsMatch(xr +1) do inc(xr);
  1347. ffs.Push(xl, xr, y, -1); // down
  1348. ffs.Push(xl, xr, y, 1); // up
  1349. while not ffs.IsEmpty do
  1350. begin
  1351. ffs.Pop(xl, xr, y, dirY);
  1352. SetCurrentY(y);
  1353. xr2 := xl;
  1354. // check left ...
  1355. if IsMatch(xl) then
  1356. begin
  1357. while (xl > 0) and IsMatch(xl-1) do dec(xl);
  1358. if xl <= xr2 -2 then
  1359. ffs.Push(xl, xr2-2, y, -dirY);
  1360. while (xr2 < maxX) and IsMatch(xr2+1) do inc(xr2);
  1361. ffs.Push(xl, xr2, y, dirY);
  1362. if xr2 >= xr +2 then
  1363. ffs.Push(xr+2, xr2, y, -dirY);
  1364. xl := xr2 +2;
  1365. end;
  1366. // check right ...
  1367. while (xl <= xr) and not IsMatch(xl) do inc(xl);
  1368. while (xl <= xr) do
  1369. begin
  1370. xr2 := xl;
  1371. while (xr2 < maxX) and IsMatch(xr2+1) do inc(xr2);
  1372. ffs.Push(xl, xr2, y, dirY);
  1373. if xr2 >= xr +2 then
  1374. begin
  1375. ffs.Push(xr+2, xr2, y, -dirY);
  1376. break;
  1377. end;
  1378. inc(xl, 2);
  1379. while (xl <= xr) and not IsMatch(xl) do inc(xl);
  1380. end;
  1381. end;
  1382. finally
  1383. ffs.Free;
  1384. end;
  1385. end;
  1386. //------------------------------------------------------------------------------
  1387. procedure TFloodFillMask.SetCurrentY(y: Integer);
  1388. begin
  1389. colorsRow := PColor32Array(img.PixelRow[y]);
  1390. maskRow := PColor32Array(mask.PixelRow[y]);
  1391. end;
  1392. //------------------------------------------------------------------------------
  1393. function TFloodFillMask.IsMatch(x: Integer): Boolean;
  1394. var
  1395. b: Byte;
  1396. begin
  1397. if (maskRow[x] > 0) then
  1398. result := false
  1399. else
  1400. begin
  1401. b := compareFunc(initialColor, colorsRow[x]);
  1402. result := b < tolerance;
  1403. if Result then
  1404. maskRow[x] := tolerance - b else
  1405. maskRow[x] := 1;
  1406. end;
  1407. end;
  1408. //------------------------------------------------------------------------------
  1409. function GetFloodFillMask(imgIn, imgMaskOut: TImage32; x, y: Integer;
  1410. tolerance: Byte; compareFunc: TCompareFunctionEx): Boolean;
  1411. var
  1412. ffm: TFloodFillMask;
  1413. begin
  1414. if not Assigned(compareFunc) then compareFunc := CompareRGBEx;
  1415. ffm := TFloodFillMask.Create;
  1416. try
  1417. Result := ffm.Execute(imgIn, imgMaskOut, x, y, tolerance, compareFunc);
  1418. finally
  1419. ffm.Free;
  1420. end;
  1421. end;
  1422. //------------------------------------------------------------------------------
  1423. procedure FloodFill(img: TImage32; x, y: Integer; newColor: TColor32;
  1424. tolerance: Byte; compareFunc: TCompareFunctionEx);
  1425. var
  1426. i: Integer;
  1427. pc, pm: PColor32;
  1428. mask: TImage32;
  1429. begin
  1430. if not assigned(compareFunc) then
  1431. begin
  1432. compareFunc := CompareRGBEx;
  1433. if tolerance = 0 then
  1434. tolerance := FloodFillDefaultRGBTolerance;
  1435. end;
  1436. mask := TImage32.Create;
  1437. try
  1438. if not GetFloodFillMask(img, mask, x, y, tolerance, compareFunc) then
  1439. Exit;
  1440. pc := img.PixelBase;
  1441. pm := mask.PixelBase;
  1442. for i := 0 to img.Width * img.Height -1 do
  1443. begin
  1444. if (pm^ > 1) then pc^ := newColor;
  1445. inc(pm); inc(pc);
  1446. end;
  1447. finally
  1448. mask.free;
  1449. end;
  1450. end;
  1451. //------------------------------------------------------------------------------
  1452. // EMBOSS - AND SUPPORT FUNCTIONS
  1453. //------------------------------------------------------------------------------
  1454. function IncPWeightColor(pwc: PWeightedColor; cnt: Integer): PWeightedColor;
  1455. begin
  1456. result := PWeightedColor(PByte(pwc) + cnt * SizeOf(TWeightedColor));
  1457. end;
  1458. //------------------------------------------------------------------------------
  1459. function Intensity(color: TColor32): byte;
  1460. var
  1461. c: TARGB absolute color;
  1462. begin
  1463. Result := (c.R * 61 + c.G * 174 + c.B * 21) shr 8;
  1464. end;
  1465. //------------------------------------------------------------------------------
  1466. function Gray(color: TColor32): TColor32;
  1467. var
  1468. c: TARGB absolute color;
  1469. res: TARGB absolute Result;
  1470. begin
  1471. res.A := c.A;
  1472. res.R := Intensity(color);
  1473. res.G := res.R;
  1474. res.B := res.R;
  1475. end;
  1476. //------------------------------------------------------------------------------
  1477. procedure Emboss(img: TImage32; radius: Integer;
  1478. depth: Integer; luminance: Integer; preserveColor: Boolean);
  1479. var
  1480. yy,xx, x,y, w,h: Integer;
  1481. b: byte;
  1482. kernel: array [0 .. MaxBlur, 0 .. MaxBlur] of Integer;
  1483. wca: TArrayOfWeightedColor;
  1484. pc0, pcf, pcb: PColor32; // pointers to pixels (forward & backward in kernel)
  1485. pw0, pw: PWeightedColor; // pointers to weight
  1486. customGray: TColor32;
  1487. pc: PColor32;
  1488. const
  1489. maxDepth = 50;
  1490. begin
  1491. // grayscale luminance as percent where 0% is black and 100% is white
  1492. //(luminance is ignored when preserveColor = true)
  1493. luminance := ClampRange(luminance, 0, 100);
  1494. b := luminance *255 div 100;
  1495. customGray := $FF000000 + b shl 16 + b shl 8 + b;
  1496. ClampRange(radius, 1, 5);
  1497. inc(depth);
  1498. ClampRange(depth, 2, maxDepth);
  1499. kernel[0][0] := 1;
  1500. for y := 1 to radius do
  1501. for x := 1 to radius do
  1502. kernel[y][x] := depth;
  1503. w := img.Width; h := img.Height;
  1504. // nb: dynamic arrays are zero-initialized (unless they're a function result)
  1505. SetLength(wca, w * h);
  1506. pc0 := IncPColor32(img.PixelBase, radius * w);
  1507. pw0 := @wca[radius * w];
  1508. for y := radius to h -1 - radius do
  1509. begin
  1510. for x := radius to w -1 - radius do
  1511. begin
  1512. pw := IncPWeightColor(pw0, x);
  1513. pcb := IncPColor32(pc0, x - 1);
  1514. if preserveColor then
  1515. begin
  1516. pcf := IncPColor32(pc0, x);
  1517. pw^.Add(pcf^, kernel[0,0]);
  1518. inc(pcf);
  1519. end else
  1520. begin
  1521. pw^.Add(customGray, kernel[0,0]);
  1522. pcf := IncPColor32(pc0, x + 1);
  1523. end;
  1524. // parse the kernel ...
  1525. for yy := 1 to radius do
  1526. begin
  1527. for xx := 1 to radius do
  1528. begin
  1529. pw^.Subtract(Gray(pcf^), kernel[yy,xx]);
  1530. pw^.Add(Gray(pcb^), kernel[yy,xx]);
  1531. dec(pcb); inc(pcf);
  1532. end;
  1533. dec(pcb, img.Width - radius);
  1534. inc(pcf, img.Width - radius);
  1535. end;
  1536. end;
  1537. inc(pc0, img.Width);
  1538. inc(pw0, img.Width);
  1539. end;
  1540. pc := @img.Pixels[0]; pw := @wca[0];
  1541. for x := 0 to img.width * img.Height - 1 do
  1542. begin
  1543. pc^ := pw.Color or $FF000000;
  1544. inc(pc); inc(pw);
  1545. end;
  1546. end;
  1547. //------------------------------------------------------------------------------
  1548. // RamerDouglasPeucker - and support functions
  1549. //------------------------------------------------------------------------------
  1550. procedure RDP(const path: TPathD; startIdx, endIdx: integer;
  1551. epsilonSqrd: double; var flags: TArrayOfInteger);
  1552. var
  1553. i, idx: integer;
  1554. d, maxD: double;
  1555. begin
  1556. idx := 0;
  1557. maxD := 0;
  1558. for i := startIdx +1 to endIdx -1 do
  1559. begin
  1560. // PerpendicularDistSqrd - avoids expensive Sqrt()
  1561. d := PerpendicularDistSqrd(path[i], path[startIdx], path[endIdx]);
  1562. if d <= maxD then Continue;
  1563. maxD := d;
  1564. idx := i;
  1565. end;
  1566. if maxD < epsilonSqrd then Exit;
  1567. flags[idx] := 1;
  1568. if idx > startIdx + 1 then RDP(path, startIdx, idx, epsilonSqrd, flags);
  1569. if endIdx > idx + 1 then RDP(path, idx, endIdx, epsilonSqrd, flags);
  1570. end;
  1571. //------------------------------------------------------------------------------
  1572. function RamerDouglasPeucker(const path: TPathD;
  1573. epsilon: double): TPathD;
  1574. var
  1575. i,j, len: integer;
  1576. buffer: TArrayOfInteger;
  1577. begin
  1578. len := length(path);
  1579. if len < 5 then
  1580. begin
  1581. result := Copy(path, 0, len);
  1582. Exit;
  1583. end;
  1584. SetLength(buffer, len); // buffer is zero initialized
  1585. buffer[0] := 1;
  1586. buffer[len -1] := 1;
  1587. RDP(path, 0, len -1, Sqr(epsilon), buffer);
  1588. j := 0;
  1589. SetLength(Result, len);
  1590. for i := 0 to len -1 do
  1591. if buffer[i] = 1 then
  1592. begin
  1593. Result[j] := path[i];
  1594. inc(j);
  1595. end;
  1596. SetLength(Result, j);
  1597. end;
  1598. //------------------------------------------------------------------------------
  1599. function RamerDouglasPeucker(const paths: TPathsD;
  1600. epsilon: double): TPathsD;
  1601. var
  1602. i,j, len: integer;
  1603. begin
  1604. j := 0;
  1605. len := length(paths);
  1606. setLength(Result, len);
  1607. for i := 0 to len -1 do
  1608. begin
  1609. Result[j] := RamerDouglasPeucker(paths[i], epsilon);
  1610. if Result[j] <> nil then inc(j);
  1611. end;
  1612. setLength(Result, j);
  1613. end;
  1614. //------------------------------------------------------------------------------
  1615. function GetNext(current, high: integer; var flags: array of Boolean): integer;
  1616. begin
  1617. Result := current +1;
  1618. while (Result <= high) and flags[Result] do inc(Result);
  1619. if (Result <= high) then Exit;
  1620. Result := 0;
  1621. while (flags[Result]) do inc(Result);
  1622. end;
  1623. //---------------------------------------------------------------------------
  1624. function GetPrior(current, high: integer; var flags: array of Boolean): integer;
  1625. begin
  1626. Result := current;
  1627. if (Result = 0) then Result := high
  1628. else dec(Result);
  1629. while (Result > 0) and flags[Result] do dec(Result);
  1630. if not flags[Result] then Exit;
  1631. Result := high;
  1632. while flags[Result] do dec(Result);
  1633. end;
  1634. //---------------------------------------------------------------------------
  1635. type
  1636. PSimplifyRec = ^TSimplifyRec;
  1637. TSimplifyRec = record
  1638. pt : TPointD;
  1639. pdSqrd : double;
  1640. prev : PSimplifyRec;
  1641. next : PSimplifyRec;
  1642. isEndPt : Boolean;
  1643. end;
  1644. function SimplifyPath(const path: TPathD;
  1645. shapeTolerance: double; isClosedPath: Boolean): TPathD;
  1646. var
  1647. i, iPrev, iNext, len, minLen: integer;
  1648. tolSqrd: double;
  1649. srArray: array of TSimplifyRec;
  1650. current, last: PSimplifyRec;
  1651. begin
  1652. Result := nil;
  1653. len := Length(path);
  1654. if not isClosedPath then minLen := 2 else minLen := 3;
  1655. if len < minLen then Exit;
  1656. SetLength(srArray, len);
  1657. for i := 0 to len -1 do
  1658. with srArray[i] do
  1659. begin
  1660. iPrev := ModEx(i-1, len);
  1661. iNext := ModEx(i+1, len);
  1662. pt := path[i];
  1663. prev := @srArray[iPrev];
  1664. next := @srArray[iNext];
  1665. pdSqrd := PerpendicularDistSqrd(path[i], path[iPrev], path[iNext]);
  1666. isEndPt := not isClosedPath and ((i = 0) or (i = len -1));
  1667. end;
  1668. current := @srArray[0];
  1669. last := current.prev;
  1670. tolSqrd := Sqr(shapeTolerance);
  1671. while current <> last do
  1672. begin
  1673. if not current.isEndPt and
  1674. ((current.pdSqrd < tolSqrd) and (current.next.pdSqrd > current.pdSqrd)) then
  1675. begin
  1676. current.prev.next := current.next;
  1677. current.next.prev := current.prev;
  1678. last := current.prev;
  1679. dec(len);
  1680. if last.next = last.prev then break;
  1681. last.pdSqrd := PerpendicularDistSqrd(last.pt, last.prev.pt, last.next.pt);
  1682. current := last.next;
  1683. current.pdSqrd := PerpendicularDistSqrd(current.pt, current.prev.pt, current.next.pt);
  1684. end
  1685. else
  1686. current := current.next;
  1687. end;
  1688. if len < minLen then Exit;
  1689. if not isClosedPath then current := @srArray[0];
  1690. NewPointDArray(Result, len, True);
  1691. for i := 0 to len -1 do
  1692. begin
  1693. Result[i] := current.pt;
  1694. current := current.next;
  1695. end;
  1696. end;
  1697. //------------------------------------------------------------------------------
  1698. function SimplifyPaths(const paths: TPathsD;
  1699. shapeTolerance: double; isClosedPath: Boolean): TPathsD;
  1700. var
  1701. i,j, len: integer;
  1702. begin
  1703. len := Length(paths);
  1704. SetLength(Result, len);
  1705. j := 0;
  1706. for i := 0 to len -1 do
  1707. begin
  1708. result[j] := SimplifyPath(paths[i], shapeTolerance, isClosedPath);
  1709. if Length(result[j]) > 0 then inc(j);
  1710. end;
  1711. SetLength(Result, j);
  1712. end;
  1713. //---------------------------------------------------------------------------
  1714. //---------------------------------------------------------------------------
  1715. type
  1716. PSimplifyExRec = ^TSimplifyExRec;
  1717. TSimplifyExRec = record
  1718. pt : TPointD;
  1719. pdSqrd : double;
  1720. segLenSq : double;
  1721. prev : PSimplifyExRec;
  1722. next : PSimplifyExRec;
  1723. end;
  1724. function DeleteCurrent(var current: PSimplifyExRec): Boolean;
  1725. var
  1726. next: PSimplifyExRec;
  1727. begin
  1728. current.prev.next := current.next;
  1729. current.next.prev := current.prev;
  1730. current := current.prev;
  1731. next := current.next;
  1732. Result := next <> current.prev;
  1733. if not Result then Exit;
  1734. next.pdSqrd := PerpendicularDistSqrd(next.pt, next.prev.pt, next.next.pt);
  1735. current.segLenSq := DistanceSqrd(current.pt, current.next.pt);
  1736. current.pdSqrd := PerpendicularDistSqrd(current.pt, current.prev.pt, current.next.pt);
  1737. end;
  1738. //---------------------------------------------------------------------------
  1739. function SimplifyPathEx(const path: TPathD; shapeTolerance: double): TPathD;
  1740. var
  1741. i, prevI, nextI, len: integer;
  1742. shapeTolSqr: double;
  1743. srArray: array of TSimplifyExRec;
  1744. current, start: PSimplifyExRec;
  1745. begin
  1746. Result := nil;
  1747. len := Length(path);
  1748. if len < 3 then Exit;
  1749. shapeTolSqr := Sqr(shapeTolerance);
  1750. SetLength(srArray, len);
  1751. for i := 0 to len -1 do
  1752. begin
  1753. prevI := i -1;
  1754. nextI := i +1;
  1755. if i = 0 then prevI := len -1
  1756. else if i = len -1 then nextI := 0;
  1757. with srArray[i] do
  1758. begin
  1759. pt := path[i];
  1760. segLenSq:= DistanceSqrd(path[i], path[nextI]);
  1761. pdSqrd := PerpendicularDistSqrd(path[i], path[prevI], path[nextI]);
  1762. prev := @srArray[prevI];
  1763. next := @srArray[nextI];
  1764. end;
  1765. end;
  1766. current := @srArray[0];
  1767. start := current.prev;
  1768. while current <> start do
  1769. begin
  1770. // Irrespective of segment length, remove vertices that deviate very little
  1771. // from imaginary lines that pass through their adjacent vertices.
  1772. // However, if the following vertex has an even sorter distance from its
  1773. // respective imaginary line, its important to remove that vertex first.
  1774. if ((current.pdSqrd < shapeTolSqr) and
  1775. (current.pdSqrd < current.next.pdSqrd)) then
  1776. begin
  1777. dec(len);
  1778. if not DeleteCurrent(current) then Break;
  1779. start := current.prev;
  1780. end
  1781. // also remove insignificant path zig-zags
  1782. else if (current.prev.segLenSq < shapeTolSqr) and
  1783. (current.segLenSq < shapeTolSqr) and
  1784. ((CrossProduct(current.prev.pt, current.pt, current.next.pt) > 0) <>
  1785. (CrossProduct(current.pt, current.next.pt, current.next.next.pt) > 0)) then
  1786. begin
  1787. dec(len);
  1788. if not DeleteCurrent(current) then Break;
  1789. start := current.prev;
  1790. end else
  1791. current := current.next;
  1792. end;
  1793. if len < 3 then Exit;
  1794. NewPointDArray(Result, len, True);
  1795. for i := 0 to len -1 do
  1796. begin
  1797. Result[i] := current.pt;
  1798. current := current.next;
  1799. end;
  1800. end;
  1801. //------------------------------------------------------------------------------
  1802. function SimplifyPathsEx(const paths: TPathsD; shapeTolerance: double): TPathsD;
  1803. var
  1804. i,j, len: integer;
  1805. begin
  1806. len := Length(paths);
  1807. SetLength(Result, len);
  1808. j := 0;
  1809. for i := 0 to len -1 do
  1810. begin
  1811. Result[j] := SimplifyPathEx(paths[i], shapeTolerance);
  1812. if Length(Result[j]) > 0 then inc(j);
  1813. end;
  1814. SetLength(Result, len);
  1815. end;
  1816. //---------------------------------------------------------------------------
  1817. //---------------------------------------------------------------------------
  1818. function DotProdVecs(const vec1, vec2: TPointD): double;
  1819. {$IFDEF INLINE} inline; {$ENDIF}
  1820. begin
  1821. result := (vec1.X * vec2.X + vec1.Y * vec2.Y);
  1822. end;
  1823. //---------------------------------------------------------------------------
  1824. function SmoothToCubicBezier(const path: TPathD;
  1825. pathIsClosed: Boolean; maxOffset: integer): TPathD;
  1826. var
  1827. i, j, len, prev: integer;
  1828. vec: TPointD;
  1829. pl: TArrayOfDouble;
  1830. unitVecs: TPathD;
  1831. d, angle, d1,d2: double;
  1832. begin
  1833. // SmoothToCubicBezier - returns cubic bezier control points
  1834. Result := nil;
  1835. len := Length(path);
  1836. if len < 3 then Exit;
  1837. NewPointDArray(Result, len *3 +1, True);
  1838. prev := len-1;
  1839. SetLength(pl, len);
  1840. SetLength(unitVecs, len);
  1841. pl[0] := Distance(path[prev], path[0]);
  1842. unitVecs[0] := GetUnitVector(path[prev], path[0]);
  1843. for i := 0 to len -1 do
  1844. begin
  1845. if i = prev then
  1846. begin
  1847. j := 0;
  1848. end else
  1849. begin
  1850. j := i +1;
  1851. pl[j] := Distance(path[i], path[j]);
  1852. unitVecs[j] := GetUnitVector(path[i], path[j]);
  1853. end;
  1854. vec := GetAvgUnitVector(unitVecs[i], unitVecs[j]);
  1855. angle := arccos(Max(-1,Min(1,(DotProdVecs(unitVecs[i], unitVecs[j])))));
  1856. d := abs(Pi-angle)/TwoPi;
  1857. d1 := pl[i] * d;
  1858. d2 := pl[j] * d;
  1859. if maxOffset > 0 then
  1860. begin
  1861. d1 := Min(maxOffset, d1);
  1862. d2 := Min(maxOffset, d2);
  1863. end;
  1864. if i = 0 then
  1865. Result[len*3-1] := TranslatePoint(path[0], -vec.X * d1, -vec.Y * d1)
  1866. else
  1867. Result[i*3-1] := TranslatePoint(path[i], -vec.X * d1, -vec.Y * d1);
  1868. Result[i*3] := path[i];
  1869. Result[i*3+1] := TranslatePoint(path[i], vec.X * d2, vec.Y * d2);
  1870. end;
  1871. Result[len*3] := path[0];
  1872. if pathIsClosed then Exit;
  1873. Result[1] := Result[0];
  1874. dec(len);
  1875. Result[len*3-1] := Result[len*3];
  1876. SetLength(Result, Len*3 +1);
  1877. end;
  1878. //------------------------------------------------------------------------------
  1879. function SmoothToCubicBezier(const paths: TPathsD;
  1880. pathIsClosed: Boolean; maxOffset: integer = 0): TPathsD;
  1881. var
  1882. i, len: integer;
  1883. begin
  1884. len := Length(paths);
  1885. SetLength(Result, len);
  1886. for i := 0 to len -1 do
  1887. Result[i] := SmoothToCubicBezier(paths[i], pathIsClosed, maxOffset);
  1888. end;
  1889. //------------------------------------------------------------------------------
  1890. function SmoothToCubicBezier2(const path: TPathD;
  1891. pathIsClosed: Boolean; maxOffset: integer): TPathD;
  1892. var
  1893. i, j, len, prev: integer;
  1894. vec: TPointD;
  1895. pl: TArrayOfDouble;
  1896. unitVecs: TPathD;
  1897. d1,d2: double;
  1898. begin
  1899. // SmoothToCubicBezier2 - returns cubic bezier control points
  1900. Result := nil;
  1901. len := Length(path);
  1902. if len < 3 then Exit;
  1903. NewPointDArray(Result, len *3 +1);
  1904. prev := len-1;
  1905. SetLength(pl, len);
  1906. SetLength(unitVecs, len);
  1907. pl[0] := Distance(path[prev], path[0]);
  1908. unitVecs[0] := GetUnitVector(path[prev], path[0]);
  1909. for i := 0 to len -1 do
  1910. begin
  1911. if i = prev then
  1912. begin
  1913. j := 0;
  1914. end else
  1915. begin
  1916. j := i +1;
  1917. pl[j] := Distance(path[i], path[j]);
  1918. unitVecs[j] := GetUnitVector(path[i], path[j]);
  1919. end;
  1920. vec := GetAvgUnitVector(unitVecs[i], unitVecs[j]);
  1921. d1 := pl[i]/2;
  1922. d2 := pl[j]/2;
  1923. if maxOffset > 0 then
  1924. begin
  1925. d1 := Min(maxOffset, d1);
  1926. d2 := Min(maxOffset, d2);
  1927. end;
  1928. if i = 0 then
  1929. Result[len*3-1] := TranslatePoint(path[0], -vec.X * d1, -vec.Y * d1)
  1930. else
  1931. Result[i*3-1] := TranslatePoint(path[i], -vec.X * d1, -vec.Y * d1);
  1932. Result[i*3] := path[i];
  1933. Result[i*3+1] := TranslatePoint(path[i], vec.X * d2, vec.Y * d2);
  1934. end;
  1935. Result[len*3] := path[0];
  1936. if pathIsClosed then Exit;
  1937. Result[1] := Result[0];
  1938. dec(len);
  1939. Result[len*3-1] := Result[len*3];
  1940. SetLength(Result, Len*3 +1);
  1941. end;
  1942. //------------------------------------------------------------------------------
  1943. function SmoothToCubicBezier2(const paths: TPathsD;
  1944. pathIsClosed: Boolean; maxOffset: integer = 0): TPathsD;
  1945. var
  1946. i, len: integer;
  1947. begin
  1948. len := Length(paths);
  1949. SetLength(Result, len);
  1950. for i := 0 to len -1 do
  1951. Result[i] := SmoothToCubicBezier2(paths[i], pathIsClosed, maxOffset);
  1952. end;
  1953. //------------------------------------------------------------------------------
  1954. function CubicInterpolate(v1, v2, v3, v4: double;
  1955. t: double; tension: double = 0): double;
  1956. var
  1957. m0, m1, tt, ttt, tensionEx: double;
  1958. a, b: double;
  1959. begin
  1960. tt := t * t;
  1961. ttt := tt * t;
  1962. tensionEx := (1-tension) * 0.5;
  1963. m0 := (v3 - v1)*tensionEx;
  1964. m1 := (v4 - v2)*tensionEx;
  1965. a := 2*v2 - 2*v3 + m0 + m1;
  1966. b := 3*v3 -3*v2 -2*m0 - m1;
  1967. Result := a*ttt + b*tt + m0*t + v2;
  1968. end;
  1969. //------------------------------------------------------------------------------
  1970. procedure Append(var path: TPathD; const pt: TPointD);
  1971. {$IFDEF INLINE} inline; {$ENDIF}
  1972. var
  1973. len: integer;
  1974. begin
  1975. len := Length(path);
  1976. SetLengthUninit(path, len +1);
  1977. path[len] := pt;
  1978. end;
  1979. //------------------------------------------------------------------------------
  1980. function SmoothPath(const path: TPathD; isClosedPath: Boolean;
  1981. tension: double; shapeTolerance: double): TPathD;
  1982. var
  1983. i, j, highI, len, cnt: integer;
  1984. pt: TPointD;
  1985. dists: TArrayOfDouble;
  1986. const
  1987. maxInterval = 1.5;
  1988. begin
  1989. Result := nil;
  1990. len := Length(path);
  1991. if len < 3 then Exit;
  1992. SetLength(dists, len);
  1993. highI := len -1;
  1994. dists[highI] := Distance(path[highI], path[0]);
  1995. for i := 0 to highI-1 do
  1996. dists[i] := Distance(path[i], path[i+1]);
  1997. if tension > 1 then tension := 1
  1998. else if tension < -1 then tension := -1;
  1999. if tension > 0.9 then
  2000. begin
  2001. Result := path;
  2002. Exit;
  2003. end;
  2004. if isClosedPath then
  2005. for i := 0 to highI do
  2006. begin
  2007. cnt := Ceil(dists[i]/maxInterval);
  2008. Append(Result, path[i]);
  2009. for j := 1 to cnt -1 do
  2010. begin
  2011. pt.X := CubicInterpolate(
  2012. path[ModEx(i-1, len)].X,
  2013. path[i].X,
  2014. path[ModEx(i+1, len)].X,
  2015. path[ModEx(i+2, len)].X, j/cnt, tension);
  2016. pt.Y := CubicInterpolate(
  2017. path[ModEx(i-1, len)].Y,
  2018. path[i].Y,
  2019. path[ModEx(i+1, len)].Y,
  2020. path[ModEx(i+2, len)].Y, j/cnt, tension);
  2021. Append(Result, pt);
  2022. end;
  2023. end
  2024. else
  2025. begin
  2026. for i := 0 to highI -1 do
  2027. begin
  2028. cnt := Ceil(dists[i]/maxInterval);
  2029. Append(Result, path[i]);
  2030. for j := 1 to cnt -1 do
  2031. begin
  2032. pt.X := CubicInterpolate(
  2033. path[Clamp(i-1, len)].X,
  2034. path[Clamp(i, len)].X,
  2035. path[Clamp(i+1, len)].X,
  2036. path[Clamp(i+2, len)].X, j/cnt, tension);
  2037. pt.Y := CubicInterpolate(
  2038. path[Clamp(i-1, len)].Y,
  2039. path[Clamp(i, len)].Y,
  2040. path[Clamp(i+1, len)].Y,
  2041. path[Clamp(i+2, len)].Y, j/cnt, tension);
  2042. Append(Result, pt);
  2043. end;
  2044. end;
  2045. Append(Result, path[highi]);
  2046. end;
  2047. Result := SimplifyPath(Result, shapeTolerance, false);
  2048. end;
  2049. //------------------------------------------------------------------------------
  2050. function SmoothPaths(const paths: TPathsD; isClosedPath: Boolean;
  2051. tension: double = 0; shapeTolerance: double = 0.1): TPathsD;
  2052. var
  2053. i, len: integer;
  2054. begin
  2055. len := Length(paths);
  2056. SetLength(Result, len);
  2057. for i := 0 to len -1 do
  2058. Result[i] := SmoothPath(paths[i], isClosedPath, tension, shapeTolerance);
  2059. end;
  2060. //------------------------------------------------------------------------------
  2061. // GaussianBlur
  2062. //------------------------------------------------------------------------------
  2063. procedure GaussianBlur(img: TImage32; rec: TRect; radius: Integer);
  2064. var
  2065. i, w,h, highX, x,y,yy,z,startz: Integer;
  2066. expConst: double;
  2067. gaussTable: array [-MaxBlur .. MaxBlur] of integer;
  2068. wc: TWeightedColor;
  2069. wca: TArrayOfWeightedColor;
  2070. wcaColor: TArrayOfColor32;
  2071. row: PColor32Array;
  2072. wcRow: PWeightedColorArray;
  2073. imgWidth: Integer;
  2074. dst, pc: PColor32;
  2075. const
  2076. tableConst = 1024;
  2077. sigma = 3;
  2078. begin
  2079. Types.IntersectRect(rec, rec, img.Bounds);
  2080. if IsEmptyRect(rec) or (radius < 1) then Exit
  2081. else if radius > MaxBlur then radius := MaxBlur;
  2082. expConst := - 1 / (Sqr(radius) * 2 * Sqr(sigma));
  2083. gaussTable[0] := Round(tableConst * Exp(expConst));
  2084. for i := 1 to radius do
  2085. begin
  2086. gaussTable[i] := Round(tableConst * Exp(expConst * Sqr(i)));
  2087. gaussTable[-i] := gaussTable[i];
  2088. end;
  2089. RectWidthHeight(rec, w, h);
  2090. setLength(wca, w * h);
  2091. NewColor32Array(wcaColor, w * h, True);
  2092. imgWidth := img.Width;
  2093. highX := imgWidth -1;
  2094. for y := 0 to h -1 do
  2095. begin
  2096. row := PColor32Array(@img.Pixels[(y + rec.Top) * imgWidth + rec.Left]);
  2097. wcRow := PWeightedColorArray(@wca[y * w]);
  2098. for x := 0 to w -1 do
  2099. for z := max(0, x - radius) to min(highX, x + radius) do
  2100. wcRow[x].Add(row[z], gaussTable[x-z]);
  2101. end;
  2102. // calculate colors
  2103. for x := 0 to w * h - 1 do
  2104. wcaColor[x] := wca[x].Color;
  2105. dst := @img.Pixels[rec.Left + rec.Top * imgWidth];
  2106. imgWidth := imgWidth * SizeOf(TColor32); // convert to byte size
  2107. for x := 0 to w -1 do
  2108. begin
  2109. pc := dst;
  2110. inc(pc, x);
  2111. for y := 0 to h -1 do
  2112. begin
  2113. wc.Reset;
  2114. startz := max(0, y - radius);
  2115. yy := startz * w;
  2116. for z := startz to min(h -1, y + radius) do
  2117. begin
  2118. wc.Add(wcaColor[x + yy], gaussTable[y-z]);
  2119. inc(yy, w);
  2120. end;
  2121. pc^ := wc.Color;
  2122. inc(PByte(pc), imgWidth); // increment by byte size
  2123. end;
  2124. end;
  2125. end;
  2126. //------------------------------------------------------------------------------
  2127. // FastGaussian blur - and support functions
  2128. //------------------------------------------------------------------------------
  2129. //http://blog.ivank.net/fastest-gaussian-blur.html
  2130. //https://www.peterkovesi.com/papers/FastGaussianSmoothing.pdf
  2131. function BoxesForGauss(stdDev, boxCnt: integer): TArrayOfInteger;
  2132. var
  2133. i, wl, wu, m: integer;
  2134. wIdeal, mIdeal: double;
  2135. begin
  2136. NewIntegerArray(Result, boxCnt, True);
  2137. wIdeal := Sqrt((12*stdDev*stdDev/boxCnt)+1); // Ideal averaging filter width
  2138. wl := Floor(wIdeal); if not Odd(wl) then dec(wl);
  2139. mIdeal :=
  2140. (-3*stdDev*stdDev +0.25*boxCnt*wl*wl +boxCnt*wl +0.75*boxCnt)/(wl+1);
  2141. m := Floor(mIdeal) div 2; // nb: variation on Ivan Kutskir's code.
  2142. wl := (wl -1) div 2; // It's better to do this here
  2143. wu := wl+1; // than later in both BoxBlurH & BoxBlurV
  2144. for i := 0 to boxCnt -1 do
  2145. if i < m then
  2146. Result[i] := wl else
  2147. Result[i] := wu;
  2148. end;
  2149. //------------------------------------------------------------------------------
  2150. procedure FastGaussianBlur(img: TImage32;
  2151. const rec: TRect; stdDev: integer; repeats: integer);
  2152. begin
  2153. FastGaussianBlur(img, rec, stdDev, stdDev, repeats);
  2154. end;
  2155. //------------------------------------------------------------------------------
  2156. procedure BoxBlurHLine(src, dst: PColor32; srcRiOffset: nativeint;
  2157. count, w: integer; dstLast: PColor32; var v: TWeightedColor);
  2158. var
  2159. lastColor: TColor32;
  2160. val: PWeightedColor;
  2161. s, d: PColor32;
  2162. begin
  2163. lastColor := v.Color;
  2164. if count > w then
  2165. count := w;
  2166. w := w - count;
  2167. // The Delphi compiler sometimes is really stupid with
  2168. // the CPU register allocation. With this, even if no actual
  2169. // code is produced, the compiler happens to make better
  2170. // decisions.
  2171. val := @v;
  2172. s := src;
  2173. d := dst;
  2174. if count > 0 then
  2175. begin
  2176. while count > 0 do
  2177. begin
  2178. if val.AddSubtract(PColor32Array(s)[srcRiOffset], s^) then
  2179. lastColor := val.Color;
  2180. inc(s);
  2181. d^ := lastColor;
  2182. inc(d);
  2183. dec(count);
  2184. end;
  2185. count := w;
  2186. while count > 0 do
  2187. begin
  2188. d^ := lastColor;
  2189. inc(d);
  2190. dec(count);
  2191. end;
  2192. end;
  2193. while PByte(d) <= PByte(dstLast) do
  2194. begin
  2195. if val.AddNoneSubtract(s^) then
  2196. lastColor := val.Color;
  2197. inc(s);
  2198. d^ := lastColor;
  2199. inc(d);
  2200. end;
  2201. end;
  2202. //------------------------------------------------------------------------------
  2203. procedure BoxBlurH(const src, dst: TArrayOfColor32; w,h, stdDev: integer);
  2204. var
  2205. i,j, ti, li, ri, re, ovr: integer;
  2206. fv, val: TWeightedColor;
  2207. lastColor: TColor32;
  2208. stdDevW: integer;
  2209. begin
  2210. ovr := Max(0, stdDev - w);
  2211. for i := 0 to h -1 do
  2212. begin
  2213. ti := i * w;
  2214. li := ti;
  2215. ri := ti +stdDev;
  2216. re := ti +w -1; // idx of last pixel in row
  2217. fv.Reset(src[ti]);
  2218. val.Reset(src[ti], stdDev +1);
  2219. for j := 0 to stdDev -1 - ovr do
  2220. val.Add(src[ti + j]);
  2221. if ovr > 0 then val.Add(clNone32, ovr);
  2222. for j := 0 to stdDev do
  2223. begin
  2224. if ri <= re then
  2225. val.Add(src[ri]) else
  2226. val.Add(src[re]); // color of last pixel in row
  2227. inc(ri);
  2228. val.Subtract(fv);
  2229. if ti <= re then
  2230. dst[ti] := val.Color;
  2231. inc(ti);
  2232. end;
  2233. // Skip "val.Color" calculation if both for-loops are skipped anyway
  2234. stdDevW := w - stdDev*2 - 1;
  2235. if (ti <= re) or (stdDevW > 0) then
  2236. begin
  2237. if w > 4 then // prevent the call-overhead if it would be slower than the inline version
  2238. BoxBlurHLine(@src[li], @dst[ti], ri - li, re - ri + 1, stdDevW, @dst[re], val)
  2239. else
  2240. begin
  2241. lastColor := val.Color;
  2242. for j := stdDevW downto 1 do
  2243. begin
  2244. if ri <= re then
  2245. begin
  2246. if val.AddSubtract(src[ri], src[li]) then
  2247. lastColor := val.Color;
  2248. inc(ri);
  2249. inc(li);
  2250. end;
  2251. dst[ti] := lastColor;
  2252. inc(ti);
  2253. end;
  2254. while ti <= re do
  2255. begin
  2256. if val.AddNoneSubtract(src[li]) then
  2257. lastColor := val.Color;
  2258. inc(li);
  2259. dst[ti] := lastColor;
  2260. inc(ti);
  2261. end;
  2262. end;
  2263. end;
  2264. end;
  2265. end;
  2266. //------------------------------------------------------------------------------
  2267. procedure BoxBlurVLine(src, dst: PColor32; srcRiOffset: nativeint;
  2268. widthBytes, count, h: integer; dstLast: PColor32; var v: TWeightedColor);
  2269. var
  2270. lastColor: TColor32;
  2271. val: PWeightedColor;
  2272. s, d: PColor32;
  2273. begin
  2274. lastColor := v.Color;
  2275. if count > h then
  2276. count := h;
  2277. h := h - count;
  2278. // The Delphi compiler sometimes is really stupid with
  2279. // the CPU register allocation. With this, even if no actual
  2280. // code is produced, the compiler happens to make better
  2281. // decisions.
  2282. val := @v;
  2283. s := src;
  2284. d := dst;
  2285. if count > 0 then
  2286. begin
  2287. while count > 0 do
  2288. begin
  2289. if val.AddSubtract(PColor32Array(s)[srcRiOffset], s^) then
  2290. lastColor := val.Color;
  2291. inc(PByte(s), widthBytes);
  2292. d^ := lastColor;
  2293. inc(PByte(d), widthBytes);
  2294. dec(count);
  2295. end;
  2296. count := h;
  2297. while count > 0 do
  2298. begin
  2299. d^ := lastColor;
  2300. inc(PByte(d), widthBytes);
  2301. dec(count);
  2302. end;
  2303. end;
  2304. while PByte(d) <= PByte(dstLast) do
  2305. begin
  2306. if val.AddNoneSubtract(s^) then
  2307. lastColor := val.Color;
  2308. inc(PByte(s), widthBytes);
  2309. d^ := lastColor;
  2310. inc(PByte(d), widthBytes);
  2311. end;
  2312. end;
  2313. //------------------------------------------------------------------------------
  2314. procedure BoxBlurV(const src, dst: TArrayOfColor32; w, h, stdDev: integer);
  2315. var
  2316. i,j, ti, li, ri, re, ovr: integer;
  2317. fv, val: TWeightedColor;
  2318. lastColor: TColor32;
  2319. stdDevH: integer;
  2320. begin
  2321. ovr := Max(0, stdDev - h);
  2322. for i := 0 to w -1 do
  2323. begin
  2324. ti := i;
  2325. li := ti;
  2326. ri := ti + stdDev * w;
  2327. re := ti +w *(h-1); // idx of last pixel in column
  2328. fv.Reset(src[ti]);
  2329. val.Reset(src[ti], stdDev +1);
  2330. for j := 0 to stdDev -1 -ovr do
  2331. val.Add(src[ti + j *w]);
  2332. if ovr > 0 then val.Add(clNone32, ovr);
  2333. for j := 0 to stdDev do
  2334. begin
  2335. if ri <= re then
  2336. val.Add(src[ri]) else
  2337. val.Add(src[re]); // color of last pixel in column
  2338. inc(ri, w);
  2339. val.Subtract(fv);
  2340. if ti <= re then
  2341. dst[ti] := val.Color;
  2342. inc(ti, w);
  2343. end;
  2344. // Skip "val.Color" calculation if both for-loops are skipped anyway
  2345. stdDevH := h - stdDev*2 - 1;
  2346. if (ti <= re) or (stdDevH > 0) then
  2347. begin
  2348. if stdDevH > 4 then // prevent the call-overhead if it would be slower than the inline version
  2349. BoxBlurVLine(@src[li], @dst[ti], ri - li, w * SizeOf(TColor32), re - ri + 1, stdDevH, @dst[re], val)
  2350. else
  2351. begin
  2352. lastColor := val.Color;
  2353. for j := stdDevH downto 1 do
  2354. begin
  2355. if ri <= re then
  2356. begin
  2357. if val.AddSubtract(src[ri], src[li]) then
  2358. lastColor := val.Color;
  2359. inc(ri, w);
  2360. inc(li, w);
  2361. end;
  2362. dst[ti] := lastColor;
  2363. inc(ti, w);
  2364. end;
  2365. while ti <= re do
  2366. begin
  2367. if val.AddNoneSubtract(src[li]) then
  2368. lastColor := val.Color;
  2369. inc(li, w);
  2370. dst[ti] := lastColor;
  2371. inc(ti, w);
  2372. end;
  2373. end;
  2374. end;
  2375. end;
  2376. end;
  2377. //------------------------------------------------------------------------------
  2378. procedure FastGaussianBlur(img: TImage32;
  2379. const rec: TRect; stdDevX, stdDevY: integer; repeats: integer);
  2380. var
  2381. i,j,len, w,h: integer;
  2382. rec2: TRect;
  2383. boxesH: TArrayOfInteger;
  2384. boxesV: TArrayOfInteger;
  2385. src, dst: TArrayOfColor32;
  2386. blurFullImage: Boolean;
  2387. pSrc, pDst: PColor32;
  2388. begin
  2389. if not Assigned(img) then Exit;
  2390. Types.IntersectRect(rec2, rec, img.Bounds);
  2391. if IsEmptyRect(rec2) then Exit;
  2392. blurFullImage := RectsEqual(rec2, img.Bounds);
  2393. RectWidthHeight(rec2, w, h);
  2394. if (Min(w, h) < 2) or ((stdDevX < 1) and (stdDevY < 1)) then Exit;
  2395. len := w * h;
  2396. NewColor32Array(src, len, True); // content is overwritten in BoxBlurH
  2397. if blurFullImage then
  2398. begin
  2399. // Use the img.Pixels directly instead of copying the entire image into 'dst'.
  2400. // The first thing the code does is BoxBlurH({source:=}dst, {dest:=}src, ...).
  2401. dst := img.Pixels;
  2402. end
  2403. else
  2404. begin
  2405. // copy a rectangular region into 'dst'
  2406. NewColor32Array(dst, len, True);
  2407. pSrc := img.PixelRow[rec2.Top];
  2408. inc(pSrc, rec2.Left);
  2409. pDst := @dst[0];
  2410. for i := 0 to h -1 do
  2411. begin
  2412. Move(pSrc^, pDst^, w * SizeOf(TColor32));
  2413. inc(pSrc, img.Width);
  2414. inc(pDst, w);
  2415. end;
  2416. end;
  2417. // do the blur
  2418. inc(repeats); // now represents total iterations
  2419. boxesH := BoxesForGauss(stdDevX, repeats);
  2420. if stdDevY = stdDevX then
  2421. boxesV := boxesH else
  2422. boxesV := BoxesForGauss(stdDevY, repeats);
  2423. img.BeginUpdate;
  2424. try
  2425. for j := 0 to repeats -1 do
  2426. begin
  2427. BoxBlurH(dst, src, w, h, boxesH[j]);
  2428. BoxBlurV(src, dst, w, h, boxesV[j]);
  2429. end;
  2430. if not blurFullImage then
  2431. begin
  2432. // copy dst array back to image rect
  2433. pDst := img.PixelRow[rec2.Top];
  2434. inc(pDst, rec2.Left);
  2435. pSrc := @dst[0];
  2436. for i := 0 to h -1 do
  2437. begin
  2438. Move(pSrc^, pDst^, w * SizeOf(TColor32));
  2439. inc(pSrc, w);
  2440. inc(pDst, img.Width);
  2441. end;
  2442. end;
  2443. finally
  2444. img.EndUpdate;
  2445. end;
  2446. end;
  2447. //------------------------------------------------------------------------------
  2448. end.