ugraph.pas 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UGraph;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, bgrabitmap, bgrabitmaptypes, LazPaintType, Graphics, BGRALayers, LCLType,
  7. BCComboBox;
  8. var
  9. NicePointMaxRadius: integer = 6;
  10. FrameDashLength: integer = 4;
  11. FramePenWidth: integer = 10;
  12. CanvasScale: integer = 1;
  13. function ComputeRatio(ARatio: string): single;
  14. function RatioToStr(ARatio: single): string;
  15. function InverseRatio(ARatio: string): string;
  16. function RectUnion(const rect1,Rect2: TRect): TRect;
  17. function RectInter(const rect1,Rect2: TRect): TRect;
  18. function RectOfs(const ARect: TRect; ofsX,ofsY: integer): TRect;
  19. function GetShapeBounds(const pts: array of TPointF; width: single): TRect;
  20. function DoPixelate(source: TBGRABitmap; pixelSize: integer; quality: string): TBGRABitmap;
  21. procedure DrawCheckers(bmp : TBGRABitmap; ARect: TRect; AScale: single = 1);
  22. procedure DrawGrid(bmp: TBGRABitmap; sizex,sizey: single; ofsx,ofsy: single);
  23. function ComputeAngle(dx,dy: single): single;
  24. function GetSelectionCenter(bmp: TBGRABitmap): TPointF;
  25. procedure ComputeSelectionMask(image: TBGRABitmap; destMask: TBGRABitmap; ARect: TRect);
  26. procedure SubstractMask(image: TBGRABitmap; DestX,DestY: Integer; mask: TBGRABitmap; SourceMaskRect: TRect);
  27. function NiceFrameBounds(ACanvasScale: integer; APoints: array of TPointF): TRect;
  28. function NiceFrame(ABitmap: TBGRABitmap; ACanvasScale: integer; APoints: array of TPointF;
  29. AColor1, AColor2: TBGRAPixel): TRect;
  30. function NicePointBounds(x,y: single): TRect;
  31. function NicePoint(bmp: TBGRABitmap; x,y: single; alpha: byte = 192):TRect; overload;
  32. function NicePoint(bmp: TBGRABitmap; ptF: TPointF; alpha: byte = 192):TRect; overload;
  33. procedure NiceLine(bmp: TBGRABitmap; x1,y1,x2,y2: single; alpha: byte = 192);
  34. function NiceText(bmp: TBGRABitmap; x,y,bmpWidth,bmpHeight: integer; s: string; align: TAlignment = taLeftJustify; valign: TTextLayout = tlTop): TRect;
  35. function ComputeColorCircle(tx,ty: integer; light: word; hueCorrection: boolean = true): TBGRABitmap;
  36. procedure RenderCloudsOn(bmp: TBGRABitmap; color: TBGRAPixel);
  37. procedure RenderWaterOn(bmp: TBGRABitmap; waterColor, skyColor: TBGRAPixel);
  38. function CreateMetalFloorTexture(tx: integer): TBGRABitmap;
  39. function CreatePlastikTexture(tx,ty: integer): TBGRABitmap;
  40. function CreateCamouflageTexture(tx,ty: integer): TBGRABitmap;
  41. function CreateSnowPrintTexture(tx,ty: integer): TBGRABitmap;
  42. function CreateRoundStoneTexture(tx,ty: integer): TBGRABitmap;
  43. function CreateStoneTexture(tx,ty: integer): TBGRABitmap;
  44. function CreateWaterTexture(tx,ty: integer): TBGRABitmap;
  45. function CreateMarbleTexture(tx,ty: integer): TBGRABitmap;
  46. function CreateWoodTexture(tx,ty: integer): TBGRABitmap;
  47. function CreateVerticalWoodTexture(tx,ty: integer): TBGRABitmap;
  48. function ClearTypeFilter(source: TBGRACustomBitmap): TBGRACustomBitmap;
  49. function ClearTypeInverseFilter(source: TBGRACustomBitmap): TBGRACustomBitmap;
  50. function WaveDisplacementFilter(source: TBGRACustomBitmap;
  51. ARect: TRect; ACenter: TPointF;
  52. AWaveLength, ADisplacement, APhase: single): TBGRACustomBitmap;
  53. function DoResample(source :TBGRABitmap; newWidth, newHeight: integer; StretchMode: TResampleMode): TBGRABitmap;
  54. procedure DrawPenStyle(AComboBox: TBCComboBox; ARect: TRect; APenStyle: TPenStyle; State: TOwnerDrawState); overload;
  55. procedure DrawPenStyle(ABitmap: TBGRABitmap; ARect: TRect; APenStyle: TPenStyle; c: TBGRAPixel); overload;
  56. procedure DrawArrow(AComboBox: TBCComboBox; ARect: TRect; AStart: boolean; AKindStr: string; ALineCap: TPenEndCap; State: TOwnerDrawState); overload;
  57. procedure DrawArrow(ABitmap: TBGRABitmap; ARect: TRect; AStart: boolean; AKindStr: string; ALineCap: TPenEndCap; AColor: TBGRAPixel); overload;
  58. function ChangeCanvasSizeOrigin(oldWidth,oldHeight,newWidth, newHeight: integer; anchor: string): TPoint;
  59. function ChangeBitmapCanvasSize(bmp: TBGRABitmap; ofs: TPoint; oldWidth,oldHeight,newWidth, newHeight: integer;
  60. anchor: string; background: TBGRAPixel; repeatImage: boolean; flipMode: boolean = false): TBGRABitmap;
  61. implementation
  62. uses GraphType, math, Types, FileUtil, dialogs, BGRAAnimatedGif,
  63. BGRAGradients, BGRATextFX, uresourcestrings, LCScaleDPI,
  64. BGRAThumbnail, LCVectorPolyShapes, BGRAPolygon, BGRAPen;
  65. function ComputeRatio(ARatio: string): single;
  66. var
  67. idxCol,errPos: Integer;
  68. num,denom: double;
  69. begin
  70. result := 0;
  71. ARatio := stringreplace(ARatio,FormatSettings.DecimalSeparator,'.',[rfReplaceAll]);
  72. if ARatio = '' then exit;
  73. idxCol := pos(':',ARatio);
  74. if idxCol = 0 then exit;
  75. val(copy(ARatio,1,idxCol-1),num,errPos);
  76. if errPos <> 0 then exit;
  77. if num < 0 then exit;
  78. val(copy(ARatio,idxCol+1,length(ARatio)-idxCol),denom,errPos);
  79. if errPos <> 0 then exit;
  80. if denom <= 0 then exit;
  81. result := num/denom;
  82. end;
  83. function RatioToStr(ARatio: single): string;
  84. var
  85. num,denom: integer;
  86. procedure InvFrac;
  87. var temp: integer;
  88. begin
  89. temp := num;
  90. num := denom;
  91. denom := temp;
  92. end;
  93. procedure AddFrac(AValue: integer);
  94. begin
  95. inc(num, AValue*denom);
  96. end;
  97. const MaxDev = 6;
  98. MaxDenom = 99;
  99. var
  100. dev: array[1..MaxDev] of integer;
  101. devCount, i: integer;
  102. curVal, remain: Single;
  103. begin
  104. if ARatio < 0 then ARatio := -ARatio;
  105. curVal := ARatio;
  106. devCount := 0;
  107. repeat
  108. inc(devCount);
  109. dev[devCount] := trunc(curVal);
  110. remain := frac(curVal);
  111. if abs(remain) < 1e-3 then break;
  112. if devCount = MaxDev then
  113. begin
  114. if remain > 0.5 then inc(dev[devCount]);
  115. break;
  116. end;
  117. curVal := 1/remain;
  118. until false;
  119. repeat
  120. num := dev[devCount];
  121. denom := 1;
  122. for i := devCount-1 downto 1 do
  123. begin
  124. InvFrac;
  125. AddFrac(dev[i]);
  126. end;
  127. if ((num >= denom) and (denom <= MaxDenom))
  128. or ((num < denom) and (num <= MaxDenom))
  129. or (devCount = 1) then break;
  130. dec(devCount);
  131. until false;
  132. result := IntToStr(num)+':'+IntToStr(denom);
  133. end;
  134. function InverseRatio(ARatio: string): string;
  135. var
  136. elements: TStringArray;
  137. begin
  138. elements := ARatio.Split([':']);
  139. if length(elements) = 2 then
  140. result := elements[1] + ':' + elements[0]
  141. else
  142. result := ARatio;
  143. end;
  144. function RectUnion(const rect1, Rect2: TRect): TRect;
  145. begin
  146. if IsRectEmpty(rect1) then
  147. begin
  148. if IsRectEmpty(rect2) then
  149. result := EmptyRect
  150. else
  151. result:= rect2;
  152. end else
  153. begin
  154. result := rect1;
  155. if not IsRectEmpty(rect2) then
  156. UnionRect(result,result,rect2);
  157. end;
  158. end;
  159. function RectInter(const rect1, Rect2: TRect): TRect;
  160. begin
  161. result := EmptyRect;
  162. IntersectRect(result,rect1,rect2);
  163. end;
  164. function RectOfs(const ARect: TRect; ofsX, ofsY: integer): TRect;
  165. begin
  166. result := ARect;
  167. OffsetRect(result,ofsX,ofsY);
  168. end;
  169. function GetShapeBounds(const pts: array of TPointF; width: single): TRect;
  170. var ix,iy,i: integer;
  171. begin
  172. width /= 2;
  173. result.Left := high(Integer);
  174. result.Top := high(Integer);
  175. result.Right := low(Integer);
  176. result.Bottom := low(Integer);
  177. for i := 0 to high(pts) do
  178. if not isEmptyPointF(pts[i]) then
  179. begin
  180. ix := floor(pts[i].x - width);
  181. iy := floor(pts[i].y - width);
  182. if ix < result.left then result.left := ix;
  183. if iy < result.Top then result.top := iy;
  184. ix := ceil(pts[i].x + width)+1;
  185. iy := ceil(pts[i].y + width)+1;
  186. if ix > result.right then result.right := ix;
  187. if iy > result.bottom then result.bottom := iy;
  188. end;
  189. if (result.right <= result.left) or (result.bottom <= result.top) then
  190. result := EmptyRect;
  191. end;
  192. function DoPixelate(source: TBGRABitmap; pixelSize: integer; quality: string): TBGRABitmap;
  193. var
  194. filter: TResampleFilter;
  195. useFilter: boolean;
  196. begin
  197. if quality = rsMitchell then
  198. filter := rfMitchell else
  199. if quality = rsSpline then
  200. filter := rfSpline
  201. else
  202. filter := rfLinear;
  203. useFilter := quality <> rsFast;
  204. result := source.FilterPixelate(pixelSize,useFilter,filter) as TBGRABitmap;
  205. end;
  206. procedure DrawCheckers(bmp: TBGRABitmap; ARect: TRect; AScale: single = 1);
  207. begin
  208. DrawThumbnailCheckers(bmp, ARect, False, AScale);
  209. end;
  210. procedure DrawGrid(bmp: TBGRABitmap; sizex, sizey: single; ofsx,ofsy: single);
  211. var xb,yb: integer;
  212. imgGrid: TBGRABitmap;
  213. alpha: byte;
  214. begin
  215. ofsx := ofsx - floor(ofsx/sizex)*sizex;
  216. ofsy := ofsy - floor(ofsy/sizey)*sizey;
  217. imgGrid := TBGRABitmap.Create(bmp.Width,1);
  218. alpha := min(96,round((abs(sizex)+abs(sizey))*(96/16/2)));
  219. imgGrid.DrawLineAntialias(0,0,imgGrid.width-1,0,BGRA(255,255,255,alpha),BGRA(0,0,0,alpha),
  220. min(3,max(1,round(sizex/8))),true);
  221. for yb := 1 to ceil(bmp.Height/sizey) do
  222. begin
  223. bmp.PutImage(0,round(ofsy),imgGrid,dmFastBlend);
  224. ofsy += sizey;
  225. end;
  226. imgGrid.Free;
  227. imgGrid := TBGRABitmap.Create(1,bmp.Height);
  228. imgGrid.DrawLineAntialias(0,0,0,imgGrid.height-1,BGRA(0,0,0,alpha),BGRA(255,255,255,alpha),
  229. min(3,max(1,round(sizey/8))),true);
  230. for xb := 1 to ceil(bmp.Width/sizex) do
  231. begin
  232. bmp.PutImage(round(ofsx),0,imgGrid,dmFastBlend);
  233. ofsx += sizex;
  234. end;
  235. imgGrid.Free;
  236. end;
  237. procedure RenderCloudsOn(bmp: TBGRABitmap; color: TBGRAPixel);
  238. const minDensity=180; maxDensity=240;
  239. var i,k,x,y: integer;
  240. fact,radius: single;
  241. tempBmp: TBGRABitmap;
  242. ptemp: PBGRAPixel;
  243. begin
  244. if color.alpha = 0 then exit;
  245. tempBmp := TBGRABitmap.Create(bmp.width,bmp.Height,BGRABlack);
  246. fact := (bmp.width+bmp.Height)/15;
  247. for i := 120 downto 20 do
  248. begin
  249. for k := 1 to 2 do
  250. begin
  251. radius := ((i+random(50))/100)*fact;
  252. x := random(bmp.Width);
  253. y := random(bmp.Height);
  254. tempBmp.GradientFill(floor(x-radius),floor(y-radius),ceil(x+radius),ceil(y+radius),BGRA(255,255,255,128),BGRAPixelTransparent,gtRadial,pointf(x,y),pointf(x+radius+0.5,y),dmFastBlend,false);
  255. end;
  256. end;
  257. ptemp := tempBmp.Data;
  258. for i := tempBmp.nbPixels-1 downto 0 do
  259. begin
  260. if ptemp^.red < minDensity then ptemp^:= BGRAPixelTransparent else
  261. if ptemp^.red > maxDensity then ptemp^:= color else
  262. ptemp^ := BGRA(color.red,color.green,color.blue,color.alpha*(ptemp^.red-minDensity) div (maxDensity-minDensity));
  263. inc(ptemp);
  264. end;
  265. bmp.PutImage(0,0,tempBmp,dmDrawWithTransparency);
  266. tempBmp.free;
  267. end;
  268. procedure RenderWaterOn(bmp: TBGRABitmap; waterColor, skyColor: TBGRAPixel);
  269. var Noise,Temp: TBGRABitmap;
  270. Phong: TPhongShading;
  271. begin
  272. Noise := CreateCyclicPerlinNoiseMap(bmp.Width,bmp.Height,1,1,1.2);
  273. Temp := Noise.FilterBlurRadial(1,rbFast) as TBGRABitmap;
  274. Noise.Free;
  275. Noise := Temp;
  276. Noise.ApplyGlobalOpacity(waterColor.alpha);
  277. waterColor.alpha := 255;
  278. Phong := TPhongShading.Create;
  279. Phong.NegativeDiffusionFactor := 0.1;
  280. Phong.AmbientFactor := 0.7;
  281. Phong.LightSourceDistanceFactor := 0;
  282. Phong.LightDestFactor := 0;
  283. Phong.LightSourceIntensity := 300;
  284. Phong.LightPosition := Point(-500,-500);
  285. Phong.LightColor := skyColor;
  286. Phong.Draw(bmp,Noise,30,0,0,waterColor);
  287. Noise.Free;
  288. Phong.Free;
  289. end;
  290. function Interp256(value1,value2,position: integer): integer; inline;
  291. begin
  292. result := (value1*(256-position)+value2*position) shr 8;
  293. end;
  294. function Interp256(color1,color2: TBGRAPixel; position: integer): TBGRAPixel; inline;
  295. begin
  296. result.red := Interp256(color1.red,color2.red,position);
  297. result.green := Interp256(color1.green,color2.green,position);
  298. result.blue := Interp256(color1.blue,color2.blue,position);
  299. result.alpha := Interp256(color1.alpha,color2.alpha,position);
  300. end;
  301. function CreateWoodTexture(tx,ty: integer): TBGRABitmap;
  302. var
  303. colorOscillation, globalColorVariation: integer;
  304. p: PBGRAPixel;
  305. i: Integer;
  306. begin
  307. result := CreateCyclicPerlinNoiseMap(tx,ty,1.5,1.5,1,rfBestQuality);
  308. p := result.Data;
  309. for i := 0 to result.NbPixels-1 do
  310. begin
  311. colorOscillation := round(sqrt((sin(p^.red*Pi/16)+1)/2)*256);
  312. globalColorVariation := p^.red;
  313. p^:= Interp256( Interp256(BGRA(247,188,120),BGRA(255,218,170),colorOscillation),
  314. Interp256(BGRA(157,97,60),BGRA(202,145,112),colorOscillation), globalColorVariation);
  315. inc(p);
  316. end;
  317. end;
  318. function CreateVerticalWoodTexture(tx, ty: integer): TBGRABitmap;
  319. var
  320. globalPos: single;
  321. colorOscillation, globalColorVariation: integer;
  322. p: PBGRAPixel;
  323. i: Integer;
  324. x,nbVertical: integer;
  325. begin
  326. result := CreateCyclicPerlinNoiseMap(tx,ty,1,1,1,rfBestQuality);
  327. p := result.Data;
  328. x := 0;
  329. nbVertical := tx div 128;
  330. if nbVertical = 0 then nbVertical := 1;
  331. for i := 0 to result.NbPixels-1 do
  332. begin
  333. globalPos := p^.red*Pi/32 + nbVertical*x*2*Pi/tx*8;
  334. colorOscillation := round(sqrt((sin(globalPos)+1)/2)*256);
  335. globalColorVariation := p^.red; //round(sin(globalPos/8)*128+128);
  336. p^:= Interp256( Interp256(BGRA(247,188,120),BGRA(255,218,170),colorOscillation),
  337. Interp256(BGRA(157,97,60),BGRA(202,145,112),colorOscillation), globalColorVariation);
  338. inc(p);
  339. inc(x);
  340. if x = tx then x := 0;
  341. end;
  342. end;
  343. function ClearTypeFilter(source: TBGRACustomBitmap): TBGRACustomBitmap;
  344. var
  345. mul3,temp: TBGRACustomBitmap;
  346. xb,yb: integer;
  347. pmul3,pdest: PBGRAPixel;
  348. a: byte;
  349. begin
  350. source.ResampleFilter := rfSpline;
  351. mul3 := source.Resample(source.Width*3-2,source.Height);
  352. temp := source.NewBitmap(source.Width*3,source.Height);
  353. temp.PutImage(1,0,mul3,dmSet);
  354. for yb := 0 to temp.Height-1 do
  355. begin
  356. temp.SetPixel(0,yb,temp.GetPixel(1,yb));
  357. temp.SetPixel(temp.Width-1,yb,temp.GetPixel(temp.Width-2,yb));
  358. end;
  359. mul3.free;
  360. mul3 := temp;
  361. result := source.NewBitmap(source.Width,source.Height);
  362. for yb := 0 to result.Height-1 do
  363. begin
  364. pmul3 := mul3.ScanLine[yb];
  365. pdest := result.ScanLine[yb];
  366. for xb := result.width-1 downto 0 do
  367. begin
  368. a := (pmul3+1)^.alpha;
  369. if a = 0 then pdest^:= BGRAPixelTransparent
  370. else
  371. begin
  372. pdest^.alpha := a;
  373. if pmul3^.alpha = 0 then pdest^.red := 128 else
  374. pdest^.red := pmul3^.red;
  375. pdest^.green := (pmul3+1)^.green;
  376. if (pmul3+2)^.alpha = 0 then pdest^.blue := 128 else
  377. pdest^.blue := (pmul3+2)^.blue;
  378. end;
  379. inc(pdest);
  380. inc(pmul3,3);
  381. end;
  382. end;
  383. mul3.free;
  384. end;
  385. function ClearTypeInverseSubFilter(source: TBGRACustomBitmap): TBGRACustomBitmap;
  386. const
  387. blueA = 20;
  388. blueB = 0;
  389. blueC = 2;
  390. redA = 20;
  391. redB = 0;
  392. redC = 2;
  393. maxV = 255;
  394. var yb,xb: integer;
  395. psrc,pdest,pgray: PBGRAPixel;
  396. a,v: integer;
  397. grayscale,temp: TBGRACustomBitmap;
  398. function Merge3(c1, c2, c3: TBGRAPixel): TBGRAPixel;
  399. var c123: cardinal;
  400. begin
  401. if (c1.alpha = 0) then
  402. Result := MergeBGRA(c2,c3)
  403. else
  404. if (c2.alpha = 0) then
  405. Result := MergeBGRA(c1,c3)
  406. else
  407. if (c3.alpha = 0) then
  408. Result := MergeBGRA(c1,c2)
  409. else
  410. begin
  411. c123 := c1.alpha + c2.alpha + c3.alpha;
  412. Result.red := (c1.red * c1.alpha + c2.red * c2.alpha + c3.red * c3.alpha + c123 shr 1) div c123;
  413. Result.green := (c1.green * c1.alpha + c2.green * c2.alpha + c3.green * c3.alpha + c123 shr 1) div c123;
  414. Result.blue := (c1.blue * c1.alpha + c2.blue * c2.alpha + c3.blue * c3.alpha + c123 shr 1) div c123;
  415. Result.alpha := (c123 + 1) div 3;
  416. end;
  417. end;
  418. begin
  419. if source.width <= 1 then
  420. begin
  421. result := source.duplicate;
  422. exit;
  423. end;
  424. grayscale := source;
  425. temp := source.NewBitmap(source.Width,source.Height);
  426. for yb := 0 to source.Height-1 do
  427. begin
  428. psrc := source.Scanline[yb];
  429. pgray := grayscale.ScanLine[yb];
  430. pdest := temp.Scanline[yb];
  431. pdest^.red := psrc^.red;
  432. pdest^.green := psrc^.green;
  433. pdest^.alpha := psrc^.alpha;
  434. a := (psrc^.alpha*blueA) - ((psrc+1)^.alpha*(blueB));
  435. if a > 0 then
  436. begin
  437. v := ((integer(psrc^.blue)*blueA)*psrc^.alpha - integer((psrc+1)^.blue*blueB)*(psrc+1)^.alpha) div a;
  438. if v >= maxV then
  439. pdest^.blue := 255 else
  440. if v > 0 then
  441. pdest^.blue := v
  442. else
  443. pdest^.blue := 0;
  444. end
  445. else
  446. pdest^.blue := psrc^.blue;
  447. inc(pdest);
  448. inc(psrc);
  449. inc(pgray);
  450. for xb := source.width-3 downto 0 do
  451. begin
  452. pdest^.green := psrc^.green;
  453. pdest^.alpha := psrc^.alpha;
  454. a := (psrc^.alpha*redA) - ((psrc-1)^.alpha*(redB));
  455. if a > 0 then
  456. begin
  457. v := ((integer(psrc^.red)*redA)*psrc^.alpha - integer((psrc-1)^.red*redB+((pgray-1)^.green-pgray^.green)*redC)*(psrc-1)^.alpha) div a;
  458. if v >= maxV then
  459. pdest^.red := 255 else
  460. if v > 0 then
  461. pdest^.red := v
  462. else
  463. pdest^.red := 0;
  464. end
  465. else
  466. pdest^.red := psrc^.red;
  467. a := (psrc^.alpha*blueA) - ((psrc+1)^.alpha*(blueB));
  468. if a > 0 then
  469. begin
  470. v := ((integer(psrc^.blue)*blueA)*psrc^.alpha - integer((psrc+1)^.blue*blueB+((pgray+1)^.green-pgray^.green)*blueC)*(psrc+1)^.alpha) div a;
  471. if v >= maxV then
  472. pdest^.blue := 255 else
  473. if v > 0 then
  474. pdest^.blue := v
  475. else
  476. pdest^.blue := 0;
  477. end
  478. else
  479. pdest^.blue := psrc^.blue;
  480. inc(pdest);
  481. inc(psrc);
  482. inc(pgray);
  483. end;
  484. pdest^.green := psrc^.green;
  485. pdest^.blue := psrc^.blue;
  486. pdest^.alpha := psrc^.alpha;
  487. a := (psrc^.alpha*redA) - ((psrc-1)^.alpha*(redB));
  488. if a > 0 then
  489. begin
  490. v := ((integer(psrc^.red)*redA)*psrc^.alpha - integer((psrc-1)^.red*redB)*(psrc-1)^.alpha) div a;
  491. if v >= maxV then
  492. pdest^.red := 255 else
  493. if v > 0 then
  494. pdest^.red := v
  495. else
  496. pdest^.red := 0;
  497. end
  498. else
  499. pdest^.red := psrc^.red;
  500. end;
  501. result := temp;
  502. end;
  503. function ClearTypeSharpenFilter(source, diffbmp: TBGRACustomBitmap): TBGRACustomBitmap;
  504. const
  505. factnum = 3;
  506. factdenom = 5;
  507. var
  508. xb,yb,maxx: integer;
  509. psrc,pdest,pdiff: PBGRAPixel;
  510. d1,d2 : integer;
  511. function clamp(value: integer) : byte;
  512. begin
  513. if value <= 0 then result := 0
  514. else if value >= 255 then result := 255
  515. else result := value;
  516. end;
  517. function adjustDiff(ref,v1,v2: integer): integer;
  518. begin
  519. v1 -= ref;
  520. v2 -= ref;
  521. result := v1+v2;
  522. end;
  523. begin
  524. if diffbmp = nil then diffbmp := source;
  525. if (source.width <= 1) or (diffbmp.width <> source.width) or (diffbmp.height <> source.height) then
  526. begin
  527. result := source.Duplicate();
  528. exit;
  529. end;
  530. result := source.NewBitmap(source.Width,source.Height);
  531. for yb := 0 to result.Height-1 do
  532. begin
  533. psrc := source.ScanLine[yb];
  534. pdest := result.ScanLine[yb];
  535. pdiff := diffbmp.ScanLine[yb];
  536. maxx := result.width-1;
  537. for xb := 0 to maxx do
  538. begin
  539. if psrc^.alpha <> 0 then
  540. begin
  541. if (xb > 0) and ((psrc-1)^.alpha <>0) and (xb < maxx) and ((psrc+1)^.alpha <>0) then
  542. begin
  543. d1 := BGRADiff((pdiff-1)^,pdiff^);
  544. d2 := BGRADiff((pdiff+1)^,pdiff^);
  545. if (d1 > 20) and (d2 > 20) and (d1+d2 > 100) then
  546. begin
  547. pdest^.red := clamp(psrc^.red - (adjustDiff(psrc^.red,(psrc+1)^.red,(psrc-1)^.red))*factnum div (2*factdenom));
  548. pdest^.green := psrc^.green;
  549. pdest^.blue := clamp(psrc^.blue - (adjustDiff(psrc^.blue,(psrc+1)^.blue,(psrc-1)^.blue))*factnum div (2*factdenom));
  550. pdest^.alpha := psrc^.alpha;
  551. end
  552. else
  553. pdest^ := psrc^;
  554. end else
  555. if (xb < maxx) and ((psrc+1)^.alpha <>0) then
  556. begin
  557. pdest^.red := clamp(psrc^.red - ((psrc+1)^.red-psrc^.red) *factnum div factdenom);
  558. pdest^.green := psrc^.green;
  559. pdest^.blue := clamp(psrc^.blue - ((psrc+1)^.blue-psrc^.blue) *factnum div factdenom);
  560. pdest^.alpha := psrc^.alpha;
  561. end else
  562. if (xb > 0) and ((psrc-1)^.alpha <>0) then
  563. begin
  564. pdest^.red := clamp(psrc^.red - ((psrc-1)^.red-psrc^.red)*factnum div factdenom);
  565. pdest^.green := psrc^.green;
  566. pdest^.blue := clamp(psrc^.blue - ((psrc-1)^.blue-psrc^.blue)*factnum div factdenom);
  567. pdest^.alpha := psrc^.alpha;
  568. end else
  569. pdest^ := psrc^;
  570. end else
  571. pdest^ := BGRAPixelTransparent;
  572. inc(pdest);
  573. inc(psrc);
  574. inc(pdiff);
  575. end;
  576. end;
  577. end;
  578. function ClearTypeRemoveContradiction(source: TBGRACustomBitmap): TBGRACustomBitmap;
  579. var
  580. xb,yb: integer;
  581. dr,db: integer;
  582. ratio: single;
  583. psrc,pdest: PBGRAPixel;
  584. begin
  585. if source.width <= 1 then
  586. begin
  587. result := source.Duplicate();
  588. exit;
  589. end;
  590. result := source.NewBitmap(source.Width,source.Height);
  591. for yb := 0 to result.Height-1 do
  592. begin
  593. psrc := source.ScanLine[yb];
  594. pdest := result.ScanLine[yb];
  595. pdest^ := psrc^;
  596. for xb := result.width-2 downto 0 do
  597. begin
  598. (pdest+1)^ := (psrc+1)^;
  599. if (psrc^.alpha > 10) and ((psrc+1)^.alpha > 10) then
  600. begin
  601. dr := psrc^.red-(psrc+1)^.red;
  602. db := psrc^.blue-(psrc+1)^.blue;
  603. if ((db < 0) and (dr > 0)) or
  604. ((db > 0) and (dr < 0)) then
  605. begin
  606. ratio := abs(dr/db);
  607. if (ratio > 0.2) and (ratio < 5) then
  608. begin
  609. dr := (psrc^.red*psrc^.alpha+(psrc+1)^.red*(psrc+1)^.alpha) div (psrc^.alpha+(psrc+1)^.alpha);
  610. db := (psrc^.blue*psrc^.alpha+(psrc+1)^.blue*(psrc+1)^.alpha) div (psrc^.alpha+(psrc+1)^.alpha);
  611. pdest^.red := dr;
  612. pdest^.blue := db;
  613. (pdest+1)^.red := dr;
  614. (pdest+1)^.blue := db;
  615. end;
  616. end;
  617. end;
  618. inc(pdest);
  619. inc(psrc);
  620. end;
  621. end;
  622. end;
  623. function ClearTypeInverseFilter(source: TBGRACustomBitmap): TBGRACustomBitmap;
  624. var
  625. mul3,temp: TBGRACustomBitmap;
  626. xb,yb: integer;
  627. pmul3,pdest: PBGRAPixel;
  628. a: byte;
  629. begin
  630. source.ResampleFilter := rfSpline;
  631. mul3 := source.Resample(source.Width*3-2,source.Height);
  632. temp := source.NewBitmap(source.Width*3,source.Height);
  633. temp.PutImage(1,0,mul3,dmSet);
  634. for yb := 0 to temp.Height-1 do
  635. begin
  636. temp.SetPixel(0,yb,temp.GetPixel(1,yb));
  637. temp.SetPixel(temp.Width-1,yb,temp.GetPixel(temp.Width-2,yb));
  638. end;
  639. mul3.free;
  640. mul3 := temp;
  641. result := source.NewBitmap(source.Width,source.Height);
  642. for yb := 0 to result.Height-1 do
  643. begin
  644. pmul3 := mul3.ScanLine[yb];
  645. pdest := result.ScanLine[yb];
  646. for xb := result.width-1 downto 0 do
  647. begin
  648. a := (pmul3+1)^.alpha;
  649. if a = 0 then pdest^:= BGRAPixelTransparent
  650. else
  651. begin
  652. pdest^.alpha := a;
  653. if (pmul3+2)^.alpha = 0 then pdest^.red := 128 else
  654. pdest^.red := (pmul3+2)^.red;
  655. pdest^.green := (pmul3+1)^.green;
  656. if pmul3^.alpha = 0 then pdest^.blue := 128 else
  657. pdest^.blue := pmul3^.blue;
  658. end;
  659. inc(pdest);
  660. inc(pmul3,3);
  661. end;
  662. end;
  663. mul3.free;
  664. temp := ClearTypeRemoveContradiction(result);
  665. result.free;
  666. result := temp;
  667. temp := result;
  668. result := ClearTypeSharpenFilter(temp,source);
  669. temp.Free;
  670. temp := ClearTypeRemoveContradiction(result);
  671. result.free;
  672. result := temp;
  673. end;
  674. type
  675. { TWaveDisplacementScanner }
  676. TWaveDisplacementScanner = class(TBGRACustomScanner)
  677. Source: TBGRACustomBitmap;
  678. Center: TPointF;
  679. Wavelength, Displacement, PhaseRad: single;
  680. function ScanAt(X,Y: Single): TBGRAPixel; override;
  681. end;
  682. { TWaveDisplacementScanner }
  683. function TWaveDisplacementScanner.ScanAt(X, Y: Single): TBGRAPixel;
  684. var
  685. u, disp: TPointF;
  686. dist: Single;
  687. alpha: ValReal;
  688. begin
  689. u := PointF(X,Y)-Center;
  690. dist := VectLen(u);
  691. if dist = 0 then disp := PointF(0,0) else
  692. begin
  693. u := u*(1/dist);
  694. alpha := PhaseRad+dist*2*Pi/Wavelength;
  695. disp := u*sin(alpha)*Displacement;
  696. end;
  697. result := Source.GetPixel(x+disp.x,y+disp.y);
  698. end;
  699. function WaveDisplacementFilter(source: TBGRACustomBitmap; ARect: TRect;
  700. ACenter: TPointF; AWaveLength, ADisplacement, APhase: single): TBGRACustomBitmap;
  701. var scan: TWaveDisplacementScanner;
  702. begin
  703. scan := TWaveDisplacementScanner.Create;
  704. scan.Center := ACenter;
  705. scan.Source := source;
  706. scan.Wavelength := AWaveLength;
  707. scan.Displacement := ADisplacement;
  708. scan.PhaseRad := APhase*Pi/180;
  709. result := TBGRABitmap.Create(source.Width,source.Height);
  710. result.FillRect(ARect, scan, dmSet);
  711. scan.Free;
  712. end;
  713. function DoResample(source: TBGRABitmap; newWidth, newHeight: integer;
  714. StretchMode: TResampleMode): TBGRABitmap;
  715. begin
  716. result := source.Resample(newWidth,newHeight,StretchMode) as TBGRABitmap;
  717. end;
  718. procedure DrawArrowMask(AMask: TBGRABitmap; AStart: boolean; AKindStr: string; ALineCap: TPenEndCap);
  719. var
  720. kind: TArrowKind;
  721. x1,x2,xm1,xm2,y,w,temp: single;
  722. begin
  723. AMask.Fill(BGRABlack);
  724. kind := StrToArrowKind(AKindStr);
  725. ApplyArrowStyle(AMask.Arrow,AStart,kind,PointF(1.5,1.5));
  726. AMask.LineCap := ALineCap;
  727. w := AMask.Height/5;
  728. if w > 0 then
  729. begin
  730. x1 := w*2.5;
  731. x2 := 0;
  732. xm1 := 0;
  733. xm2 := w*2.5;
  734. if kind in[akNone,akCut] then x1 -= w*0.7 else
  735. if kind in[akFlipped,akFlippedCut] then x1 += w*0.7;
  736. if not AStart then
  737. begin
  738. temp := x1;
  739. x1 := -x2;
  740. x2 := -temp;
  741. end else
  742. begin
  743. xm1 := (AMask.Width-0.5)-xm1;
  744. xm2 := (AMask.Width-0.5)-xm2;
  745. end;
  746. x1 -= 0.5;
  747. x2 += AMask.Width-0.5;
  748. y := (AMask.Height-1)/2;
  749. if kind in[akTail,akNone,akTip] then w *= 2;
  750. AMask.DrawLineAntialias(x1,y,x2,y,BGRAWhite,w);
  751. if AMask.Width > AMask.Height*2 then
  752. AMask.GradientFill(0,0,AMask.width,AMask.height,BGRABlack,BGRAPixelTransparent,gtLinear,PointF(xm1,0),PointF(xm2,0),dmDrawWithTransparency);
  753. end;
  754. end;
  755. procedure DrawPenStyle(AComboBox: TBCComboBox; ARect: TRect;
  756. APenStyle: TPenStyle; State: TOwnerDrawState);
  757. var bmp : TBGRABitmap;
  758. c,c2: TBGRAPixel;
  759. scale: Double;
  760. begin
  761. if odSelected in State then
  762. begin
  763. c := ColorToBGRA(AComboBox.DropDownFontHighlight);
  764. c2 := ColorToBGRA(AComboBox.DropDownHighlight);
  765. end
  766. else
  767. begin
  768. c := ColorToBGRA(AComboBox.DropDownFontColor);
  769. c2 := ColorToBGRA(AComboBox.DropDownColor);
  770. end;
  771. scale := AComboBox.GetCanvasScaleFactor;
  772. with Size(ARect) do bmp := TBGRABitmap.Create(round(cx*scale),round(cy*scale),c2);
  773. DrawPenStyle(bmp, bmp.ClipRect,APenStyle, c);
  774. bmp.Draw(ACombobox.Canvas,ARect,true);
  775. bmp.Free;
  776. end;
  777. procedure DrawPenStyle(ABitmap: TBGRABitmap; ARect: TRect;
  778. APenStyle: TPenStyle; c: TBGRAPixel);
  779. begin
  780. ABitmap.LineCap := pecFlat;
  781. ABitmap.PenStyle:= APenStyle;
  782. ABitmap.DrawLineAntialias(ARect.Left+ARect.Width/10-0.5,ARect.Top+ARect.Height/2-0.5,
  783. ARect.Right-ARect.Width/10-0.5,ARect.Top+ARect.Height/2-0.5, c, ARect.Width/10);
  784. end;
  785. procedure DrawArrow(AComboBox: TBCComboBox; ARect: TRect; AStart: boolean; AKindStr: string; ALineCap: TPenEndCap; State: TOwnerDrawState);
  786. var mask, bmp : TBGRABitmap;
  787. c,c2: TBGRAPixel;
  788. scale: Double;
  789. begin
  790. if odSelected in State then
  791. begin
  792. c2 := ColorToBGRA(AComboBox.DropDownHighlight);
  793. c := ColorToBGRA(AComboBox.DropDownFontHighlight);
  794. end else
  795. begin
  796. c2 := ColorToBGRA(AComboBox.DropDownColor);
  797. c := ColorToBGRA(AComboBox.DropDownFontColor);
  798. end;
  799. scale := AComboBox.GetCanvasScaleFactor;
  800. with Size(ARect) do mask:= TBGRABitmap.Create(round(cx*scale),round(cy*scale),BGRABlack);
  801. DrawArrowMask(mask, AStart, AKindStr, ALineCap);
  802. bmp := TBGRABitmap.Create(mask.Width,mask.Height,c2);
  803. bmp.FillMask(0,0,mask,c,dmDrawWithTransparency);
  804. bmp.Draw(ACombobox.Canvas,ARect,true);
  805. bmp.Free;
  806. mask.Free;
  807. end;
  808. procedure DrawArrow(ABitmap: TBGRABitmap; ARect: TRect; AStart: boolean; AKindStr: string; ALineCap: TPenEndCap; AColor: TBGRAPixel); overload;
  809. var mask: TBGRABitmap;
  810. begin
  811. with Size(ARect) do mask:= TBGRABitmap.Create(cx,cy,BGRABlack);
  812. DrawArrowMask(mask, AStart, AKindStr, ALineCap);
  813. ABitmap.FillMask(ARect.Left,ARect.Top, mask, AColor, dmDrawWithTransparency);
  814. mask.Free;
  815. end;
  816. function CreateMarbleTexture(tx,ty: integer): TBGRABitmap;
  817. var
  818. colorOscillation: integer;
  819. p: PBGRAPixel;
  820. i: Integer;
  821. begin
  822. result := CreateCyclicPerlinNoiseMap(tx,ty,0.5,0.5,0.8,rfBestQuality);
  823. p := result.Data;
  824. for i := 0 to result.NbPixels-1 do
  825. begin
  826. colorOscillation := round(sqrt(sqrt((sin(p^.red*Pi/128+0.5)+1)/2))*256);
  827. p^ := Interp256(BGRA(161,117,105),BGRA(218,197,180),colorOscillation);
  828. inc(p);
  829. end;
  830. end;
  831. function CreateWaterTexture(tx,ty: integer): TBGRABitmap;
  832. const blurSize = 5;
  833. var
  834. temp: TBGRABitmap;
  835. phong: TPhongShading;
  836. begin
  837. result := CreateCyclicPerlinNoiseMap(tx,ty,1,1,1.2,rfBestQuality);
  838. temp:= result.GetPart(rect(-blurSize,-blurSize,tx+blurSize,ty+blurSize)) as TBGRABitmap;
  839. BGRAReplace(temp,temp.FilterBlurRadial(blurSize,rbFast));
  840. phong := TPhongShading.Create;
  841. phong.LightSourceDistanceFactor := 0;
  842. phong.LightDestFactor := 0;
  843. phong.LightSourceIntensity := 150;
  844. phong.LightPositionZ := 80;
  845. phong.LightColor := BGRA(105,233,240);
  846. phong.NegativeDiffusionFactor := 0.3;
  847. phong.SpecularIndex := 20;
  848. phong.AmbientFactor := 0.4;
  849. phong.Draw(result,temp,20,-blurSize,-blurSize,BGRA(28,139,166));
  850. phong.Free;
  851. temp.Free;
  852. end;
  853. function CreateStoneTexture(tx,ty: integer): TBGRABitmap;
  854. var
  855. temp: TBGRABitmap;
  856. phong: TPhongShading;
  857. begin
  858. result := CreateCyclicPerlinNoiseMap(tx,ty,1,1,0.6);
  859. temp:= result.GetPart(rect(-2,-2,tx+2,ty+2)) as TBGRABitmap;
  860. phong := TPhongShading.Create;
  861. phong.LightSourceDistanceFactor := 0;
  862. phong.LightDestFactor := 0;
  863. phong.LightSourceIntensity := 100;
  864. phong.LightPositionZ := 100;
  865. phong.NegativeDiffusionFactor := 0.3;
  866. phong.AmbientFactor := 0.5;
  867. phong.Draw(result,temp,30,-2,-2,BGRA(170,170,170));
  868. phong.Free;
  869. temp.Free;
  870. end;
  871. function CreateRoundStoneTexture(tx,ty: integer): TBGRABitmap;
  872. var
  873. temp: TBGRABitmap;
  874. phong: TPhongShading;
  875. begin
  876. result := CreateCyclicPerlinNoiseMap(tx,ty,1,1,1.2,rfBestQuality);
  877. temp:= result.GetPart(rect(-2,-2,tx+2,ty+2)) as TBGRABitmap;
  878. BGRAReplace(temp,temp.FilterBlurRadial(2,rbFast));
  879. phong := TPhongShading.Create;
  880. phong.LightSourceDistanceFactor := 0;
  881. phong.LightDestFactor := 0;
  882. phong.LightSourceIntensity := 70;
  883. phong.LightPositionZ := 100;
  884. phong.NegativeDiffusionFactor := 0;
  885. phong.SpecularIndex := 10;
  886. phong.AmbientFactor := 0.5;
  887. phong.LightColor := BGRA(255,255,192);
  888. phong.Draw(result,temp,30,-2,-2,BGRA(170,170,170));
  889. phong.Free;
  890. temp.Free;
  891. end;
  892. function CreateSnowPrintTexture(tx,ty: integer): TBGRABitmap;
  893. var
  894. v: single;
  895. p: PBGRAPixel;
  896. i: Integer;
  897. temp: TBGRABitmap;
  898. phong: TPhongShading;
  899. begin
  900. result := CreateCyclicPerlinNoiseMap(tx,ty,1,1,1.2,rfBestQuality);
  901. p := result.Data;
  902. for i := 0 to result.NbPixels-1 do
  903. begin
  904. v := p^.red;
  905. if v > 80 then v := (v-80)/10+80;
  906. if v < 50 then v := 50-(50-v)/10;
  907. p^ := MapHeightToBGRA(v/255,255);
  908. inc(p);
  909. end;
  910. temp:= result.GetPart(rect(-2,-2,tx+2,ty+2)) as TBGRABitmap;
  911. phong := TPhongShading.Create;
  912. phong.LightSourceDistanceFactor := 0;
  913. phong.LightDestFactor := 0;
  914. phong.LightSourceIntensity := 100;
  915. phong.LightPositionZ := 100;
  916. phong.NegativeDiffusionFactor := 0.3;
  917. phong.Draw(result,temp,30,-2,-2,BGRAWhite);
  918. phong.Free;
  919. temp.Free;
  920. end;
  921. function CreateCamouflageTexture(tx,ty: integer): TBGRABitmap;
  922. var
  923. v: integer;
  924. p: PBGRAPixel;
  925. i: Integer;
  926. temp: TBGRABitmap;
  927. begin
  928. result := CreateCyclicPerlinNoiseMap(tx,ty,1,1,1,rfBestQuality);
  929. p := result.Data;
  930. for i := 0 to result.NbPixels-1 do
  931. begin
  932. v := p^.red;
  933. if v < 64 then p^:= BGRA(31,33,46) else
  934. if v < 128 then p^:= BGRA(89,71,57) else
  935. if v < 192 then p^:= BGRA(80,106,67) else
  936. p^:= BGRA(161,157,121);
  937. inc(p);
  938. end;
  939. temp := result.getPart(rect(-2,-2,tx+2,ty+2)) as TBGRABitmap;
  940. BGRAReplace(temp,temp.FilterMedian(moMediumSmooth));
  941. result.PutImage(-2,-2,temp,dmSet);
  942. temp.Free;
  943. end;
  944. function CreatePlastikTexture(tx,ty: integer): TBGRABitmap;
  945. const blurSize = 2;
  946. var
  947. temp: TBGRABitmap;
  948. phong: TPhongShading;
  949. p: PBGRAPixel;
  950. i: Integer;
  951. v: Byte;
  952. begin
  953. result := CreateCyclicPerlinNoiseMap(tx,ty,1,1,1);
  954. p := result.Data;
  955. for i := 0 to result.NbPixels-1 do
  956. begin
  957. v := p^.red;
  958. if v < 32 then v:= v*2 else
  959. if (v > 32) and (v < 224) then v := (v-32) div 2 + 64 else
  960. if v >= 224 then v:= (v-224)*2+(224-32) div 2;
  961. p^:= BGRA(v,v,v);
  962. inc(p);
  963. end;
  964. temp:= result.GetPart(rect(-blurSize,-blurSize,tx+blurSize,ty+blurSize)) as TBGRABitmap;
  965. BGRAReplace(temp,temp.FilterNormalize(False));
  966. BGRAReplace(temp,temp.FilterBlurMotion(ty div 6,90,False));
  967. BGRAReplace(temp,temp.FilterBlurRadial(blurSize,rbFast));
  968. phong := TPhongShading.Create;
  969. phong.LightSourceDistanceFactor := 0;
  970. phong.LightDestFactor := 0;
  971. phong.LightSourceIntensity := 300;
  972. phong.LightPositionZ := 10;
  973. phong.NegativeDiffusionFactor := 0;
  974. phong.AmbientFactor := 0.6;
  975. phong.SpecularIndex := 25;
  976. phong.SpecularFactor:= 10;
  977. phong.Draw(result,temp,10,-blurSize,-blurSize,BGRA(58,206,113));
  978. phong.Free;
  979. temp.Free;
  980. end;
  981. function CreateMetalFloorTexture(tx: integer): TBGRABitmap;
  982. var
  983. temp,noise: TBGRABitmap;
  984. phong: TPhongShading;
  985. ty: integer;
  986. begin
  987. ty := tx div 2;
  988. result := TBGRABitmap.Create(tx,ty,BGRABlack);
  989. result.FillEllipseAntialias(tx*1.2/8,ty/2,tx/20,ty/3,BGRA(240,240,240));
  990. result.FillEllipseAntialias(tx*2.8/8,ty/2,tx/20,ty/3,BGRA(240,240,240));
  991. result.FillEllipseAntialias(tx*3/4,ty*1.2/4,ty/3,tx/20,BGRA(240,240,240));
  992. result.FillEllipseAntialias(tx*3/4,ty*2.8/4,ty/3,tx/20,BGRA(240,240,240));
  993. BGRAReplace(result,result.FilterBlurRadial(1,rbFast));
  994. noise := CreateCyclicPerlinNoiseMap(tx,ty,1,1,1);
  995. noise.FillRect(0,0,tx,ty,BGRA(0,0,0,220),dmLinearBlend);
  996. result.BlendImage(0,0,noise,boAdditive);
  997. noise.free;
  998. temp:= result.GetPart(rect(-2,-2,tx+2,ty+2)) as TBGRABitmap;
  999. phong := TPhongShading.Create;
  1000. phong.LightSourceDistanceFactor := 0;
  1001. phong.LightDestFactor := 0;
  1002. phong.LightSourceIntensity := 100;
  1003. phong.LightPositionZ := 80;
  1004. phong.NegativeDiffusionFactor := 0;
  1005. phong.AmbientFactor := 0.5;
  1006. phong.Draw(result,temp,10,-2,-2,BGRA(116,116,116));
  1007. phong.Free;
  1008. temp.Free;
  1009. end;
  1010. function ComputeAngle(dx, dy: single): single;
  1011. begin
  1012. if dy = 0 then
  1013. begin
  1014. if dx < 0 then result := 180 else result := 0;
  1015. end else
  1016. if dx = 0 then
  1017. begin
  1018. if dy < 0 then result := -90 else result := 90;
  1019. end else
  1020. begin
  1021. result := ArcTan(dy/dx)*180/Pi;
  1022. if dx < 0 then result += 180;
  1023. end;
  1024. end;
  1025. function GetSelectionCenter(bmp: TBGRABitmap): TPointF;
  1026. var xb,yb: integer; p: PBGRAPixel;
  1027. xsum,ysum,asum,alpha: single;
  1028. begin
  1029. if bmp = nil then
  1030. begin
  1031. result := pointF(0,0);
  1032. exit;
  1033. end;
  1034. xsum := 0;
  1035. ysum := 0;
  1036. asum := 0;
  1037. for yb := 0 to bmp.Height-1 do
  1038. begin
  1039. p := bmp.ScanLine[yb];
  1040. for xb := 0 to bmp.Width-1 do
  1041. begin
  1042. alpha := p^.red/255; inc(p);
  1043. xsum += xb*alpha;
  1044. ysum += yb*alpha;
  1045. asum += alpha;
  1046. end;
  1047. end;
  1048. if asum = 0 then
  1049. result := pointF(bmp.width/2-0.5, bmp.Height/2-0.5) else
  1050. result := pointF(xsum/asum,ysum/asum);
  1051. end;
  1052. procedure ComputeSelectionMask(image: TBGRABitmap; destMask: TBGRABitmap; ARect: TRect);
  1053. var
  1054. aimage: byte;
  1055. xb,yb: integer; pimage, pmask: PBGRAPixel;
  1056. begin
  1057. IntersectRect(ARect, ARect,rect(0,0,image.Width,image.Height));
  1058. IntersectRect(ARect, ARect,rect(0,0,destMask.Width,destMask.Height));
  1059. for yb := ARect.Top to ARect.Bottom-1 do
  1060. begin
  1061. pimage := image.ScanLine[yb]+ARect.Left;
  1062. pmask := destMask.ScanLine[yb]+ARect.Left;
  1063. for xb := ARect.Left to ARect.Right-1 do
  1064. begin
  1065. aimage := pimage^.alpha;
  1066. pmask^ := BGRA(aimage,aimage,aimage,255);
  1067. if aimage <> 0 then pimage^.alpha := 255;
  1068. inc(pimage);
  1069. inc(pmask);
  1070. end;
  1071. end;
  1072. end;
  1073. procedure SubstractMask(image: TBGRABitmap; DestX,DestY: Integer; mask: TBGRABitmap; SourceMaskRect: TRect);
  1074. var
  1075. xb,yb: integer;
  1076. pimage, pmask: PBGRAPixel;
  1077. aimage, amask: byte;
  1078. Delta: TPoint;
  1079. begin
  1080. if not IntersectRect(SourceMaskRect,SourceMaskRect,rect(0,0,mask.Width,mask.Height)) then exit;
  1081. Delta.X := - SourceMaskRect.Left + DestX;
  1082. Delta.Y := - SourceMaskRect.Top + DestY;
  1083. OffsetRect(SourceMaskRect, Delta.x, Delta.y);
  1084. if not IntersectRect(SourceMaskRect,SourceMaskRect,rect(0,0,image.Width,image.Height)) then exit;
  1085. OffsetRect(SourceMaskRect, -Delta.x, -Delta.y);
  1086. for yb := SourceMaskRect.Top to SourceMaskRect.Bottom-1 do
  1087. begin
  1088. pimage := image.ScanLine[yb+Delta.Y]+SourceMaskRect.Left+Delta.X;
  1089. pmask := mask.ScanLine[yb]+SourceMaskRect.Left;
  1090. for xb := SourceMaskRect.Left to SourceMaskRect.Right-1 do
  1091. begin
  1092. amask := pmask^.red;
  1093. if amask <> 0 then
  1094. begin
  1095. aimage := pimage^.alpha;
  1096. if aimage > amask then
  1097. pimage^.alpha := aimage-amask else
  1098. pimage^ := BGRAPixelTransparent;
  1099. end;
  1100. inc(pimage);
  1101. inc(pmask);
  1102. end;
  1103. end;
  1104. end;
  1105. function NiceFrameBounds(ACanvasScale: integer; APoints: array of TPointF): TRect;
  1106. var
  1107. rF: TRectF;
  1108. w: Extended;
  1109. pt: TPointF;
  1110. begin
  1111. w := FramePenWidth*ACanvasScale/10 / 2 + 1;
  1112. rF := EmptyRectF;
  1113. for pt in APoints do
  1114. rF := rF.Union(BGRABitmapTypes.RectF(pt + PointF(0.5, 0.5) - PointF(w, w),
  1115. pt + PointF(0.5, 0.5) + PointF(w, w)));
  1116. result := rect(floor(rF.Left), floor(rF.Top),
  1117. ceil(rF.Right), ceil(rF.Bottom));
  1118. end;
  1119. function NiceFrame(ABitmap: TBGRABitmap; ACanvasScale: integer;
  1120. APoints: array of TPointF; AColor1, AColor2: TBGRAPixel): TRect;
  1121. var
  1122. w, d: single;
  1123. filler: TBGRAMultishapeFiller;
  1124. stroker: TBGRAPenStroker;
  1125. begin
  1126. result := NiceFrameBounds(ACanvasScale, APoints);
  1127. w := FramePenWidth*ACanvasScale/10;
  1128. d := FrameDashLength*ACanvasScale;
  1129. filler := TBGRAMultishapeFiller.Create;
  1130. stroker := nil;
  1131. try
  1132. stroker := TBGRAPenStroker.Create;
  1133. stroker.JoinStyle:= pjsRound;
  1134. filler.AddPolygonStroke(APoints, AColor2, w, stroker);
  1135. stroker.CustomPenStyle := BGRAPenStyle(d/w, d/w);
  1136. filler.AddPolygonStroke(APoints, AColor1, w, stroker);
  1137. filler.PolygonOrder:= poLastOnTop;
  1138. filler.Draw(ABitmap);
  1139. finally
  1140. stroker.Free;
  1141. filler.Free;
  1142. end;
  1143. end;
  1144. function NicePointBounds(x,y: single): TRect;
  1145. var
  1146. penWidth, penWidthStroke: Single;
  1147. begin
  1148. penWidth := NicePointMaxRadius*CanvasScale / 6;
  1149. if penWidth < 1 then penWidth := 1;
  1150. penWidthStroke := penWidth * 3.5;
  1151. result := rect(floor(x-NicePointMaxRadius*CanvasScale-penWidthStroke/2)-1,
  1152. floor(y-NicePointMaxRadius*CanvasScale-penWidthStroke/2)-1,
  1153. ceil(x+NicePointMaxRadius*CanvasScale+penWidthStroke/2)+2,
  1154. ceil(y+NicePointMaxRadius*CanvasScale+penWidthStroke/2)+2);
  1155. end;
  1156. function NicePoint(bmp: TBGRABitmap; x, y: single; alpha: byte = 192): TRect;
  1157. var
  1158. multi: TBGRAMultishapeFiller;
  1159. oldClip: TRect;
  1160. penWidth, penWidthStroke: Single;
  1161. begin
  1162. result := NicePointBounds(x,y);
  1163. if not Assigned(bmp) then exit;
  1164. oldClip := bmp.ClipRect;
  1165. bmp.IntersectClip(result);
  1166. penWidth := NicePointMaxRadius*CanvasScale / 6;
  1167. if penWidth < 1 then penWidth := 1;
  1168. penWidthStroke := penWidth * 3.5;
  1169. multi := TBGRAMultishapeFiller.Create;
  1170. multi.AddEllipseBorder(x,y,NicePointMaxRadius*CanvasScale-2,
  1171. NicePointMaxRadius*CanvasScale-2, penWidthStroke, BGRA(0,0,0,alpha));
  1172. multi.AddEllipseBorder(x,y,NicePointMaxRadius*CanvasScale-2,
  1173. NicePointMaxRadius*CanvasScale-2, penWidth, BGRA(255,255,255,alpha));
  1174. multi.PolygonOrder:= poLastOnTop;
  1175. multi.Draw(bmp);
  1176. multi.Free;
  1177. bmp.ClipRect := oldClip;
  1178. end;
  1179. function NicePoint(bmp: TBGRABitmap; ptF: TPointF; alpha: byte = 192): TRect;
  1180. begin
  1181. result := NicePoint(bmp,ptF.x,ptF.y,alpha);
  1182. end;
  1183. procedure NiceLine(bmp: TBGRABitmap; x1, y1, x2, y2: single; alpha: byte = 192);
  1184. begin
  1185. if not Assigned(bmp) then exit;
  1186. bmp.DrawLineAntialias(round(x1), round(y1), round(x2), round(y2),BGRA(0,0,0,alpha),3,True);
  1187. bmp.DrawLineAntialias(round(x1), round(y1), round(x2), round(y2),BGRA(255,255,255,alpha),1,True);
  1188. end;
  1189. function NiceText(bmp: TBGRABitmap; x, y, bmpWidth,bmpHeight: integer; s: string; align: TAlignment; valign: TTextLayout): TRect;
  1190. var fx: TBGRATextEffect;
  1191. f: TFont;
  1192. ofs: integer;
  1193. previousClip: TRect;
  1194. begin
  1195. f := TFont.Create;
  1196. f.Name := 'Arial';
  1197. f.Height := DoScaleY(16*CanvasScale,OriginalDPI);
  1198. ofs := DoScaleX(4*CanvasScale,OriginalDPI);
  1199. fx := TBGRATextEffect.Create(s,f,true);
  1200. if valign = tlBottom then y := y-fx.TextSize.cy else
  1201. if valign = tlCenter then y := y-fx.TextSize.cy div 2;
  1202. if y+fx.TextSize.cy > bmpHeight then y := bmpHeight-fx.TextSize.cy;
  1203. if y < 0 then y := 0;
  1204. if align = taRightJustify then x := x-fx.TextSize.cx else
  1205. if align = taCenter then x := x-fx.TextSize.cx div 2;
  1206. if x+fx.TextSize.cx > bmpWidth then x := bmpWidth-fx.TextSize.cx;
  1207. if x < 0 then x := 0;
  1208. result := rect(x,y,x+fx.TextWidth+2*ofs,y+fx.TextHeight+2*ofs);
  1209. if Assigned(bmp) then
  1210. begin
  1211. previousClip := bmp.ClipRect;
  1212. bmp.ClipRect := result;
  1213. fx.DrawShadow(bmp,x+ofs,y+ofs,ofs,BGRABlack);
  1214. fx.DrawOutline(bmp,x,y,BGRABlack);
  1215. fx.Draw(bmp,x,y,BGRAWhite);
  1216. bmp.ClipRect := previousClip;
  1217. end;
  1218. fx.Free;
  1219. f.Free;
  1220. end;
  1221. function ComputeColorCircle(tx, ty: integer; light: word; hueCorrection: boolean = true): TBGRABitmap;
  1222. var xb,yb : integer;
  1223. pdest: PBGRAPixel;
  1224. angle,xc,yc: single;
  1225. ec: TExpandedPixel;
  1226. c: TBGRAPixel;
  1227. gray,level: Word;
  1228. begin
  1229. result := TBGRABitmap.Create(tx,ty);
  1230. result.FillEllipseAntialias(tx/2-0.5,ty/2-0.5,tx/2,ty/2,BGRABlack);
  1231. xc := tx/2-0.5;
  1232. yc := ty/2-0.5;
  1233. for yb := 0 to ty-1 do
  1234. begin
  1235. pdest := result.scanline[yb];
  1236. For xb := 0 to tx-1 do
  1237. begin
  1238. if pdest^.alpha <> 0 then
  1239. begin
  1240. ec.alpha := $FFFF;
  1241. angle := ComputeAngle(xb-xc,yb-yc);
  1242. if angle < 0 then angle += 360;
  1243. if hueCorrection then
  1244. angle := GtoH(round(angle/360*65536) and 65535)/65536*360;
  1245. if angle < 60 then
  1246. begin
  1247. ec.red := $FFFF;
  1248. ec.green := round(angle/60*$FFFF);
  1249. ec.blue := $0000;
  1250. end else
  1251. if angle < 120 then
  1252. begin
  1253. ec.red := $FFFF-round((angle-60)/60*$FFFF);
  1254. ec.green := $FFFF;
  1255. ec.blue := $0000;
  1256. end else
  1257. if angle < 180 then
  1258. begin
  1259. ec.red := $0000;
  1260. ec.green := $FFFF;
  1261. ec.blue := round((angle-120)/60*$FFFF);
  1262. end else
  1263. if angle < 240 then
  1264. begin
  1265. ec.red := $0000;
  1266. ec.green := $FFFF-round((angle-180)/60*$FFFF);
  1267. ec.blue := $FFFF;
  1268. end else
  1269. if angle < 300 then
  1270. begin
  1271. ec.red := round((angle-240)/60*$FFFF);
  1272. ec.green := $0000;
  1273. ec.blue := $FFFF;
  1274. end else
  1275. begin
  1276. ec.red := $FFFF;
  1277. ec.green := $0000;
  1278. ec.blue := $FFFF-round((angle-300)/60*$FFFF);
  1279. end;
  1280. gray := min($FFFF,max(0,$FFFF - round((sqrt(sqr((xb-xc)/(tx/2))+sqr((yb-yc)/(ty/2)))*1.2-0.1)*$FFFF)));
  1281. level := max(max(ec.red,ec.green),ec.blue);
  1282. {$hints off}
  1283. ec.red := (ec.red*(not gray)+level*gray) shr 16;
  1284. ec.green := (ec.green*(not gray)+level*gray) shr 16;
  1285. ec.blue := (ec.blue*(not gray)+level*gray) shr 16;
  1286. {$hints on}
  1287. ec.red := (ec.red*light) shr 16;
  1288. ec.green := (ec.green*light) shr 16;
  1289. ec.blue := (ec.blue*light) shr 16;
  1290. c := GammaCompression(ec);
  1291. c.alpha := pdest^.alpha;
  1292. pdest^ := c;
  1293. end;
  1294. inc(pdest);
  1295. end;
  1296. end;
  1297. end;
  1298. function ChangeCanvasSizeOrigin(oldWidth,oldHeight,newWidth, newHeight: integer; anchor: string): TPoint;
  1299. var
  1300. origin: TPoint;
  1301. begin
  1302. origin := Point((newWidth div 2)-(oldWidth div 2),(newHeight div 2)-(oldHeight div 2));
  1303. anchor := LowerCase(anchor);
  1304. if (anchor='topleft') or (anchor='top') or (anchor='topright') then origin.Y := 0;
  1305. if (anchor='bottomleft') or (anchor='bottom') or (anchor='bottomright') then origin.Y := newHeight-oldHeight;
  1306. if (anchor='topleft') or (anchor='left') or (anchor='bottomleft') then origin.X := 0;
  1307. if (anchor='topright') or (anchor='right') or (anchor='bottomright') then origin.X := newWidth-oldWidth;
  1308. result := origin;
  1309. end;
  1310. function ChangeBitmapCanvasSize(bmp: TBGRABitmap; ofs: TPoint; oldWidth,oldHeight,newWidth, newHeight: integer;
  1311. anchor: string; background: TBGRAPixel; repeatImage: boolean; flipMode: boolean = false): TBGRABitmap;
  1312. var origin: TPoint;
  1313. xb,yb: integer;
  1314. dx,dy: integer;
  1315. minx,miny,maxx,maxy: integer;
  1316. flippedImages: array[Boolean,Boolean] of TBGRABitmap;
  1317. begin
  1318. if (newWidth < 1) or (newHeight < 1) then
  1319. raise exception.Create('Invalid canvas size');
  1320. origin := ChangeCanvasSizeOrigin(oldWidth, oldHeight, newWidth, newHeight, anchor);
  1321. inc(origin.x, ofs.x);
  1322. inc(origin.y, ofs.y);
  1323. result := TBGRABitmap.Create(newWidth,newHeight, background);
  1324. dx := oldWidth;
  1325. dy := oldHeight;
  1326. if repeatImage then
  1327. begin
  1328. minx := (0-origin.X-oldWidth+1) div oldWidth;
  1329. miny := (0-origin.Y-oldHeight+1) div oldHeight;
  1330. maxx := (newWidth-origin.X+oldWidth-1) div oldWidth;
  1331. maxy := (newHeight-origin.Y+oldHeight-1) div oldHeight;
  1332. end else
  1333. begin
  1334. minx := 0;
  1335. miny := 0;
  1336. maxx := 0;
  1337. maxy := 0;
  1338. end;
  1339. if flipMode and repeatImage then
  1340. begin
  1341. flippedImages[false,false] := bmp;
  1342. if (minx <> 0) or (miny <> 0) or (maxx <> 0) or (maxy <> 0) then
  1343. begin
  1344. flippedImages[true,false] := bmp.Duplicate as TBGRABitmap;
  1345. flippedImages[true,false].HorizontalFlip;
  1346. flippedImages[true,true] := flippedImages[true,false].Duplicate as TBGRABitmap;
  1347. flippedImages[true,true].VerticalFlip;
  1348. flippedImages[false,true] := bmp.Duplicate as TBGRABitmap;
  1349. flippedImages[false,true].VerticalFlip;
  1350. end else
  1351. begin
  1352. flippedImages[true,false] := nil; //never used
  1353. flippedImages[true,true] := nil;
  1354. flippedImages[false,true] := nil;
  1355. end;
  1356. for xb := minx to maxx do
  1357. for yb := miny to maxy do
  1358. result.PutImage(origin.x+xb*dx,origin.Y+yb*dy,flippedImages[odd(xb),odd(yb)],dmSet);
  1359. flippedImages[true,false].free;
  1360. flippedImages[true,true].free;
  1361. flippedImages[false,true].free;
  1362. end else
  1363. begin
  1364. for xb := minx to maxx do
  1365. for yb := miny to maxy do
  1366. result.PutImage(origin.x+xb*dx,origin.Y+yb*dy,bmp,dmSet);
  1367. end;
  1368. end;
  1369. initialization
  1370. Randomize;
  1371. end.