2
0

uphongfilter.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UPhongFilter;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  7. StdCtrls, ExtCtrls, Spin, UFilterConnector, BGRABitmapTypes, BGRABitmap,
  8. UScripting;
  9. type
  10. { TFPhongFilter }
  11. TFPhongFilter = class(TForm)
  12. Button_Cancel: TButton;
  13. Button_OK: TButton;
  14. GroupBox_Color: TGroupBox;
  15. GroupBox_Color1: TGroupBox;
  16. Label_LightPosition: TLabel;
  17. Label_Altitude: TLabel;
  18. PaintBox1: TPaintBox;
  19. Radio_MapLinearLightness: TRadioButton;
  20. Radio_MapSaturation: TRadioButton;
  21. Radio_UseKeep: TRadioButton;
  22. Radio_UseBackColor: TRadioButton;
  23. Radio_MapLightness: TRadioButton;
  24. Radio_UsePenColor: TRadioButton;
  25. Radio_MapAlpha: TRadioButton;
  26. Radio_UseTexture: TRadioButton;
  27. Radio_MapRed: TRadioButton;
  28. Radio_MapGreen: TRadioButton;
  29. Radio_MapBlue: TRadioButton;
  30. SpinEdit_Altitude: TSpinEdit;
  31. Timer1: TTimer;
  32. procedure Button_OKClick(Sender: TObject);
  33. procedure FormCreate(Sender: TObject);
  34. procedure FormDestroy(Sender: TObject);
  35. procedure FormShow(Sender: TObject);
  36. procedure PaintBox1MouseDown(Sender: TObject; {%H-}Button: TMouseButton;
  37. {%H-}Shift: TShiftState; X, Y: Integer);
  38. procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
  39. Y: Integer);
  40. procedure PaintBox1Paint(Sender: TObject);
  41. procedure Radio_MapChange(Sender: TObject);
  42. procedure Radio_UseChange(Sender: TObject);
  43. procedure SpinEdit_AltitudeChange(Sender: TObject);
  44. procedure Timer1Timer(Sender: TObject);
  45. private
  46. { private declarations }
  47. FInitializing: boolean;
  48. FCenter: TPointF;
  49. FHeightMap: TBGRABitmap;
  50. FWorkspaceColor: TColor;
  51. FTexture: TBGRACustomBitmap;
  52. function GetCurrentLightPos: TPointF;
  53. procedure InitParams;
  54. procedure PreviewNeeded;
  55. function ComputeFilteredLayer: TBGRABitmap;
  56. public
  57. FilterConnector: TFilterConnector;
  58. property CurrentLightPos: TPointF read GetCurrentLightPos;
  59. end;
  60. function ShowPhongFilterDlg(AFilterConnector: TObject): TScriptResult;
  61. implementation
  62. uses LCScaleDPI, UMac, BGRAGradients, LazPaintType;
  63. function ShowPhongFilterDlg(AFilterConnector: TObject): TScriptResult;
  64. var
  65. FPhongFilter: TFPhongFilter;
  66. begin
  67. FPhongFilter:= TFPhongFilter.create(nil);
  68. FPhongFilter.FilterConnector := AFilterConnector as TFilterConnector;
  69. FPhongFilter.FWorkspaceColor:= FPhongFilter.FilterConnector.LazPaintInstance.Config.GetWorkspaceColor;
  70. try
  71. if FPhongFilter.FilterConnector.ActiveLayer <> nil then
  72. begin
  73. if Assigned(FPhongFilter.FilterConnector.Parameters) and
  74. FPhongFilter.FilterConnector.Parameters.Booleans['Validate'] then
  75. begin
  76. FPhongFilter.InitParams;
  77. FPhongFilter.PreviewNeeded;
  78. FPhongFilter.FilterConnector.PutImage(FPhongFilter.ComputeFilteredLayer,true,true);
  79. FPhongFilter.FilterConnector.ValidateAction;
  80. result := srOk;
  81. end else
  82. begin
  83. if FPhongFilter.showModal = mrOk then result := srOk
  84. else result := srCancelledByUser;
  85. end;
  86. end
  87. else
  88. result := srException;
  89. finally
  90. FPhongFilter.free;
  91. end;
  92. end;
  93. { TFPhongFilter }
  94. procedure TFPhongFilter.Button_OKClick(Sender: TObject);
  95. begin
  96. FilterConnector.ValidateAction;
  97. FilterConnector.LazPaintInstance.Config.SetDefaultPhongFilterAltitude(SpinEdit_Altitude.Value);
  98. FilterConnector.LazPaintInstance.ToolManager.LightPosition := CurrentLightPos;
  99. ModalResult := mrOK;
  100. end;
  101. procedure TFPhongFilter.FormCreate(Sender: TObject);
  102. begin
  103. ScaleControl(Self,OriginalDPI);
  104. CheckOKCancelBtns(Button_OK,Button_Cancel);
  105. FCenter := PointF(0.5,0.5);
  106. FWorkspaceColor:= clAppWorkspace;
  107. end;
  108. procedure TFPhongFilter.FormDestroy(Sender: TObject);
  109. begin
  110. FreeAndNil(FHeightMap);
  111. if Assigned(FTexture) then FTexture.Free;
  112. end;
  113. procedure TFPhongFilter.FormShow(Sender: TObject);
  114. begin
  115. InitParams;
  116. PreviewNeeded;
  117. end;
  118. procedure TFPhongFilter.PaintBox1MouseDown(Sender: TObject;
  119. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  120. begin
  121. FCenter := PointF(X/PaintBox1.Width*2-0.5,Y/PaintBox1.Height*2-0.5);
  122. PaintBox1.Invalidate;
  123. PreviewNeeded;
  124. end;
  125. procedure TFPhongFilter.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState;
  126. X, Y: Integer);
  127. begin
  128. if ssLeft in Shift then
  129. begin
  130. FCenter := PointF(X/PaintBox1.Width*2-0.5,Y/PaintBox1.Height*2-0.5);
  131. PaintBox1.Invalidate;
  132. PreviewNeeded;
  133. end;
  134. end;
  135. procedure TFPhongFilter.PaintBox1Paint(Sender: TObject);
  136. var x,y: integer;
  137. begin
  138. x := round((FCenter.X+0.5)*PaintBox1.Width/2);
  139. y := round((FCenter.Y+0.5)*PaintBox1.Height/2);
  140. PaintBox1.Canvas.Brush.Style := bsSolid;
  141. PaintBox1.Canvas.Brush.Color := FWorkspaceColor;
  142. PaintBox1.Canvas.Pen.Style := psSolid;
  143. PaintBox1.Canvas.Pen.Color := MergeBGRA(ColorToBGRA(clBlack),ColorToBGRA(FWorkspaceColor));
  144. PaintBox1.Canvas.Rectangle(0,0,PaintBox1.Width,PaintBox1.Height);
  145. PaintBox1.Canvas.Pen.Style := psDot;
  146. PaintBox1.Canvas.Pen.Color := clBlack;
  147. PaintBox1.Canvas.Brush.Style := bsSolid;
  148. PaintBox1.Canvas.Brush.Color := clWhite;
  149. PaintBox1.Canvas.Rectangle(PaintBox1.Width div 4,PaintBox1.Height div 4,PaintBox1.Width*3 div 4,PaintBox1.Height*3 div 4);
  150. PaintBox1.Canvas.Pen.Style := psSolid;
  151. PaintBox1.Canvas.Pen.Color := clBlack;
  152. PaintBox1.Canvas.Brush.Style := bsSolid;
  153. PaintBox1.Canvas.Brush.Color := clWhite;
  154. PaintBox1.Canvas.Ellipse(x-3,y-3,x+4,y+4);
  155. end;
  156. procedure TFPhongFilter.Radio_MapChange(Sender: TObject);
  157. begin
  158. FreeAndNil(FHeightMap);
  159. if not FInitializing then PreviewNeeded;
  160. end;
  161. procedure TFPhongFilter.Radio_UseChange(Sender: TObject);
  162. begin
  163. if not FInitializing then PreviewNeeded;
  164. end;
  165. procedure TFPhongFilter.SpinEdit_AltitudeChange(Sender: TObject);
  166. begin
  167. if SpinEdit_Altitude.Value < 6 then
  168. SpinEdit_Altitude.Increment := 1
  169. else if SpinEdit_Altitude.Value < 25 then
  170. SpinEdit_Altitude.Increment := 3
  171. else
  172. SpinEdit_Altitude.Increment := 5;
  173. if not FInitializing then PreviewNeeded;
  174. end;
  175. procedure TFPhongFilter.Timer1Timer(Sender: TObject);
  176. begin
  177. Timer1.Enabled := false;
  178. FilterConnector.PutImage(ComputeFilteredLayer,True,true);
  179. Button_OK.Enabled := true;
  180. end;
  181. procedure TFPhongFilter.PreviewNeeded;
  182. begin
  183. Timer1.Enabled := false;
  184. Timer1.Enabled := True;
  185. Button_OK.Enabled := false;
  186. end;
  187. function TFPhongFilter.GetCurrentLightPos: TPointF;
  188. begin
  189. result := PointF(FCenter.X*FilterConnector.ActiveLayer.Width,
  190. FCenter.Y*FilterConnector.ActiveLayer.Height);
  191. end;
  192. procedure TFPhongFilter.InitParams;
  193. var
  194. texOpacity: Byte;
  195. begin
  196. FInitializing:= true;
  197. Radio_UseTexture.Enabled := (FilterConnector.LazPaintInstance.ToolManager.BackFill.Texture <> nil);
  198. if FTexture <> nil then
  199. begin
  200. FTexture.FreeReference;
  201. FTexture := nil;
  202. end;
  203. if Radio_UseTexture.Enabled then
  204. begin
  205. Radio_UseTexture.Checked := true;
  206. texOpacity := FilterConnector.LazPaintInstance.ToolManager.BackFill.TextureOpacity;
  207. if texOpacity <> 255 then
  208. begin
  209. FTexture := FilterConnector.LazPaintInstance.ToolManager.BackFill.Texture.Duplicate;
  210. FTexture.ApplyGlobalOpacity(texOpacity);
  211. end else
  212. FTexture := FilterConnector.LazPaintInstance.ToolManager.BackFill.Texture.NewReference;
  213. end
  214. else Radio_UsePenColor.Checked := true;
  215. SpinEdit_Altitude.Value := FilterConnector.LazPaintInstance.Config.DefaultPhongFilterAltitude;
  216. with FilterConnector.LazPaintInstance.ToolManager.LightPosition do
  217. FCenter := PointF(X/FilterConnector.LazPaintInstance.Image.Width,
  218. Y/FilterConnector.LazPaintInstance.Image.Height);
  219. if Assigned(FilterConnector.Parameters) then
  220. with FilterConnector.Parameters do
  221. begin
  222. if IsDefined('ColorSource') then
  223. case Strings['ColorSource'] of
  224. 'Pen': Radio_UsePenColor.checked := true;
  225. 'Back': Radio_UseBackColor.checked := true;
  226. 'Layer': Radio_UseKeep.checked := true;
  227. end;
  228. if IsDefined('AltitudePercent') then
  229. SpinEdit_Altitude.Value := Integers['AltitudePercent'];
  230. if IsDefined('LightPosPercent') then
  231. FCenter := Points2D['LightPosPercent']*(1/100);
  232. if IsDefined('LightXPercent') then
  233. FCenter.x := Floats['LightXPercent']/100;
  234. if IsDefined('LightYPercent') then
  235. FCenter.y := Floats['LightYPercent']/100;
  236. if IsDefined('AltitudeSource') then
  237. case Strings['AltitudeSource'] of
  238. 'Lightness': Radio_MapLightness.Checked:= true;
  239. 'LinearLightness': Radio_MapLinearLightness.Checked:= true;
  240. 'Saturation': Radio_MapSaturation.Checked:= true;
  241. 'Alpha': Radio_MapAlpha.Checked:= true;
  242. 'Red': Radio_MapRed.Checked:= true;
  243. 'Green': Radio_MapGreen.Checked:= true;
  244. 'Blue': Radio_MapBlue.Checked:= true;
  245. end;
  246. end;
  247. SpinEdit_AltitudeChange(nil);
  248. FInitializing := false;
  249. end;
  250. procedure ScanLineMapLightness(psrc,pdest: PBGRAPixel; count: integer);
  251. const oneOver65535 = 1/65535;
  252. begin
  253. while count > 0 do
  254. begin
  255. pdest^ := MapHeightToBGRA(GetLightness(GammaExpansion(psrc^))*oneOver65535,psrc^.alpha);
  256. inc(pdest);
  257. inc(psrc);
  258. dec(count);
  259. end;
  260. end;
  261. procedure ScanLineMapLinearLightness(psrc,pdest: PBGRAPixel; count: integer);
  262. const oneOver255 = 1/255;
  263. begin
  264. while count > 0 do
  265. begin
  266. pdest^ := MapHeightToBGRA((psrc^.red*0.299+psrc^.green*0.587+psrc^.blue*0.114)*oneOver255,psrc^.alpha);
  267. inc(pdest);
  268. inc(psrc);
  269. dec(count);
  270. end;
  271. end;
  272. procedure ScanLineMapAlpha(psrc,pdest: PBGRAPixel; count: integer);
  273. begin
  274. while count > 0 do
  275. begin
  276. pdest^ := BGRA(psrc^.alpha,psrc^.alpha,psrc^.alpha,255);
  277. inc(pdest);
  278. inc(psrc);
  279. dec(count);
  280. end;
  281. end;
  282. procedure ScanLineMapBlue(psrc,pdest: PBGRAPixel; count: integer);
  283. begin
  284. while count > 0 do
  285. begin
  286. pdest^ := BGRA(psrc^.blue,psrc^.blue,psrc^.blue,psrc^.alpha);
  287. inc(pdest);
  288. inc(psrc);
  289. dec(count);
  290. end;
  291. end;
  292. procedure ScanLineMapGreen(psrc,pdest: PBGRAPixel; count: integer);
  293. begin
  294. while count > 0 do
  295. begin
  296. pdest^ := BGRA(psrc^.green,psrc^.green,psrc^.green,psrc^.alpha);
  297. inc(pdest);
  298. inc(psrc);
  299. dec(count);
  300. end;
  301. end;
  302. procedure ScanLineMapRed(psrc,pdest: PBGRAPixel; count: integer);
  303. begin
  304. while count > 0 do
  305. begin
  306. pdest^ := BGRA(psrc^.red,psrc^.red,psrc^.red,psrc^.alpha);
  307. inc(pdest);
  308. inc(psrc);
  309. dec(count);
  310. end;
  311. end;
  312. procedure ScanLineMapSaturation(psrc,pdest: PBGRAPixel; count: integer);
  313. const oneOver65535 = 1/65535;
  314. begin
  315. while count > 0 do
  316. begin
  317. with BGRAToHSLA(psrc^) do
  318. pdest^ := MapHeightToBGRA(saturation*oneOver65535,psrc^.alpha);
  319. inc(pdest);
  320. inc(psrc);
  321. dec(count);
  322. end;
  323. end;
  324. function TFPhongFilter.ComputeFilteredLayer: TBGRABitmap;
  325. var shader: TPhongShading;
  326. yb: integer;
  327. scanlineMapFunc: procedure(psrc,pdest: PBGRAPixel; count: integer);
  328. begin
  329. result := TBGRABitmap.Create(FilterConnector.ActiveLayer.Width, FilterConnector.ActiveLayer.Height);
  330. shader := TPhongShading.Create;
  331. shader.AmbientFactor := 0.5;
  332. shader.NegativeDiffusionFactor := 0.15;
  333. shader.LightPositionF := CurrentLightPos;
  334. shader.LightPositionZ := FilterConnector.LazPaintInstance.ToolManager.LightAltitude;
  335. if FHeightMap = nil then
  336. begin
  337. if Radio_MapLightness.Checked then
  338. scanlineMapFunc := @ScanLineMapLightness
  339. else if Radio_MapLinearLightness.Checked then
  340. scanlineMapFunc := @ScanLineMapLinearLightness
  341. else if Radio_MapAlpha.Checked then
  342. scanlineMapFunc := @ScanLineMapAlpha
  343. else if Radio_MapBlue.Checked then
  344. scanlineMapFunc := @ScanLineMapBlue
  345. else if Radio_MapGreen.Checked then
  346. scanlineMapFunc := @ScanLineMapGreen
  347. else if Radio_MapRed.Checked then
  348. scanlineMapFunc := @ScanLineMapRed
  349. else if Radio_MapSaturation.Checked then
  350. scanlineMapFunc := @ScanLineMapSaturation
  351. else
  352. scanlineMapFunc := nil;
  353. if Assigned(scanlineMapFunc) then
  354. begin
  355. FHeightMap := TBGRABitmap.Create(FilterConnector.BackupLayer.Width,FilterConnector.BackupLayer.Height);
  356. for yb := FilterConnector.WorkArea.Top to FilterConnector.WorkArea.Bottom-1 do
  357. scanlineMapFunc(FilterConnector.BackupLayer.ScanLine[yb]+FilterConnector.WorkArea.Left,
  358. FHeightMap.ScanLine[yb]+FilterConnector.WorkArea.Left, FilterConnector.WorkArea.Right - FilterConnector.WorkArea.Left);
  359. end;
  360. end;
  361. if FHeightMap <> nil then
  362. begin
  363. if Radio_UseTexture.Checked then
  364. shader.DrawScan(result, FHeightMap, SpinEdit_Altitude.Value, 0, 0, FTexture)
  365. else if Radio_UsePenColor.Checked then
  366. shader.Draw(result, FHeightMap, SpinEdit_Altitude.Value,0,0,FilterConnector.LazPaintInstance.ToolManager.ForeColor)
  367. else if Radio_UseKeep.Checked then
  368. shader.Draw(result, FHeightMap, SpinEdit_Altitude.Value,0,0,FilterConnector.BackupLayer)
  369. else
  370. shader.Draw(result, FHeightMap, SpinEdit_Altitude.Value,0,0,FilterConnector.LazPaintInstance.ToolManager.BackColor);
  371. end;
  372. shader.Free;
  373. end;
  374. {$R *.lfm}
  375. end.