123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- // SPDX-License-Identifier: GPL-3.0-only
- unit UPhongFilter;
- {$mode objfpc}{$H+}
- interface
- uses
- Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
- StdCtrls, ExtCtrls, Spin, UFilterConnector, BGRABitmapTypes, BGRABitmap,
- UScripting;
- type
- { TFPhongFilter }
- TFPhongFilter = class(TForm)
- Button_Cancel: TButton;
- Button_OK: TButton;
- GroupBox_Color: TGroupBox;
- GroupBox_Color1: TGroupBox;
- Label_LightPosition: TLabel;
- Label_Altitude: TLabel;
- PaintBox1: TPaintBox;
- Radio_MapLinearLightness: TRadioButton;
- Radio_MapSaturation: TRadioButton;
- Radio_UseKeep: TRadioButton;
- Radio_UseBackColor: TRadioButton;
- Radio_MapLightness: TRadioButton;
- Radio_UsePenColor: TRadioButton;
- Radio_MapAlpha: TRadioButton;
- Radio_UseTexture: TRadioButton;
- Radio_MapRed: TRadioButton;
- Radio_MapGreen: TRadioButton;
- Radio_MapBlue: TRadioButton;
- SpinEdit_Altitude: TSpinEdit;
- Timer1: TTimer;
- procedure Button_OKClick(Sender: TObject);
- procedure FormCreate(Sender: TObject);
- procedure FormDestroy(Sender: TObject);
- procedure FormShow(Sender: TObject);
- procedure PaintBox1MouseDown(Sender: TObject; {%H-}Button: TMouseButton;
- {%H-}Shift: TShiftState; X, Y: Integer);
- procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
- Y: Integer);
- procedure PaintBox1Paint(Sender: TObject);
- procedure Radio_MapChange(Sender: TObject);
- procedure Radio_UseChange(Sender: TObject);
- procedure SpinEdit_AltitudeChange(Sender: TObject);
- procedure Timer1Timer(Sender: TObject);
- private
- { private declarations }
- FInitializing: boolean;
- FCenter: TPointF;
- FHeightMap: TBGRABitmap;
- FWorkspaceColor: TColor;
- FTexture: TBGRACustomBitmap;
- function GetCurrentLightPos: TPointF;
- procedure InitParams;
- procedure PreviewNeeded;
- function ComputeFilteredLayer: TBGRABitmap;
- public
- FilterConnector: TFilterConnector;
- property CurrentLightPos: TPointF read GetCurrentLightPos;
- end;
- function ShowPhongFilterDlg(AFilterConnector: TObject): TScriptResult;
- implementation
- uses LCScaleDPI, UMac, BGRAGradients, LazPaintType;
- function ShowPhongFilterDlg(AFilterConnector: TObject): TScriptResult;
- var
- FPhongFilter: TFPhongFilter;
- begin
- FPhongFilter:= TFPhongFilter.create(nil);
- FPhongFilter.FilterConnector := AFilterConnector as TFilterConnector;
- FPhongFilter.FWorkspaceColor:= FPhongFilter.FilterConnector.LazPaintInstance.Config.GetWorkspaceColor;
- try
- if FPhongFilter.FilterConnector.ActiveLayer <> nil then
- begin
- if Assigned(FPhongFilter.FilterConnector.Parameters) and
- FPhongFilter.FilterConnector.Parameters.Booleans['Validate'] then
- begin
- FPhongFilter.InitParams;
- FPhongFilter.PreviewNeeded;
- FPhongFilter.FilterConnector.PutImage(FPhongFilter.ComputeFilteredLayer,true,true);
- FPhongFilter.FilterConnector.ValidateAction;
- result := srOk;
- end else
- begin
- if FPhongFilter.showModal = mrOk then result := srOk
- else result := srCancelledByUser;
- end;
- end
- else
- result := srException;
- finally
- FPhongFilter.free;
- end;
- end;
- { TFPhongFilter }
- procedure TFPhongFilter.Button_OKClick(Sender: TObject);
- begin
- FilterConnector.ValidateAction;
- FilterConnector.LazPaintInstance.Config.SetDefaultPhongFilterAltitude(SpinEdit_Altitude.Value);
- FilterConnector.LazPaintInstance.ToolManager.LightPosition := CurrentLightPos;
- ModalResult := mrOK;
- end;
- procedure TFPhongFilter.FormCreate(Sender: TObject);
- begin
- ScaleControl(Self,OriginalDPI);
- CheckOKCancelBtns(Button_OK,Button_Cancel);
- FCenter := PointF(0.5,0.5);
- FWorkspaceColor:= clAppWorkspace;
- end;
- procedure TFPhongFilter.FormDestroy(Sender: TObject);
- begin
- FreeAndNil(FHeightMap);
- if Assigned(FTexture) then FTexture.Free;
- end;
- procedure TFPhongFilter.FormShow(Sender: TObject);
- begin
- InitParams;
- PreviewNeeded;
- end;
- procedure TFPhongFilter.PaintBox1MouseDown(Sender: TObject;
- Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
- begin
- FCenter := PointF(X/PaintBox1.Width*2-0.5,Y/PaintBox1.Height*2-0.5);
- PaintBox1.Invalidate;
- PreviewNeeded;
- end;
- procedure TFPhongFilter.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState;
- X, Y: Integer);
- begin
- if ssLeft in Shift then
- begin
- FCenter := PointF(X/PaintBox1.Width*2-0.5,Y/PaintBox1.Height*2-0.5);
- PaintBox1.Invalidate;
- PreviewNeeded;
- end;
- end;
- procedure TFPhongFilter.PaintBox1Paint(Sender: TObject);
- var x,y: integer;
- begin
- x := round((FCenter.X+0.5)*PaintBox1.Width/2);
- y := round((FCenter.Y+0.5)*PaintBox1.Height/2);
- PaintBox1.Canvas.Brush.Style := bsSolid;
- PaintBox1.Canvas.Brush.Color := FWorkspaceColor;
- PaintBox1.Canvas.Pen.Style := psSolid;
- PaintBox1.Canvas.Pen.Color := MergeBGRA(ColorToBGRA(clBlack),ColorToBGRA(FWorkspaceColor));
- PaintBox1.Canvas.Rectangle(0,0,PaintBox1.Width,PaintBox1.Height);
- PaintBox1.Canvas.Pen.Style := psDot;
- PaintBox1.Canvas.Pen.Color := clBlack;
- PaintBox1.Canvas.Brush.Style := bsSolid;
- PaintBox1.Canvas.Brush.Color := clWhite;
- PaintBox1.Canvas.Rectangle(PaintBox1.Width div 4,PaintBox1.Height div 4,PaintBox1.Width*3 div 4,PaintBox1.Height*3 div 4);
- PaintBox1.Canvas.Pen.Style := psSolid;
- PaintBox1.Canvas.Pen.Color := clBlack;
- PaintBox1.Canvas.Brush.Style := bsSolid;
- PaintBox1.Canvas.Brush.Color := clWhite;
- PaintBox1.Canvas.Ellipse(x-3,y-3,x+4,y+4);
- end;
- procedure TFPhongFilter.Radio_MapChange(Sender: TObject);
- begin
- FreeAndNil(FHeightMap);
- if not FInitializing then PreviewNeeded;
- end;
- procedure TFPhongFilter.Radio_UseChange(Sender: TObject);
- begin
- if not FInitializing then PreviewNeeded;
- end;
- procedure TFPhongFilter.SpinEdit_AltitudeChange(Sender: TObject);
- begin
- if SpinEdit_Altitude.Value < 6 then
- SpinEdit_Altitude.Increment := 1
- else if SpinEdit_Altitude.Value < 25 then
- SpinEdit_Altitude.Increment := 3
- else
- SpinEdit_Altitude.Increment := 5;
- if not FInitializing then PreviewNeeded;
- end;
- procedure TFPhongFilter.Timer1Timer(Sender: TObject);
- begin
- Timer1.Enabled := false;
- FilterConnector.PutImage(ComputeFilteredLayer,True,true);
- Button_OK.Enabled := true;
- end;
- procedure TFPhongFilter.PreviewNeeded;
- begin
- Timer1.Enabled := false;
- Timer1.Enabled := True;
- Button_OK.Enabled := false;
- end;
- function TFPhongFilter.GetCurrentLightPos: TPointF;
- begin
- result := PointF(FCenter.X*FilterConnector.ActiveLayer.Width,
- FCenter.Y*FilterConnector.ActiveLayer.Height);
- end;
- procedure TFPhongFilter.InitParams;
- var
- texOpacity: Byte;
- begin
- FInitializing:= true;
- Radio_UseTexture.Enabled := (FilterConnector.LazPaintInstance.ToolManager.BackFill.Texture <> nil);
- if FTexture <> nil then
- begin
- FTexture.FreeReference;
- FTexture := nil;
- end;
- if Radio_UseTexture.Enabled then
- begin
- Radio_UseTexture.Checked := true;
- texOpacity := FilterConnector.LazPaintInstance.ToolManager.BackFill.TextureOpacity;
- if texOpacity <> 255 then
- begin
- FTexture := FilterConnector.LazPaintInstance.ToolManager.BackFill.Texture.Duplicate;
- FTexture.ApplyGlobalOpacity(texOpacity);
- end else
- FTexture := FilterConnector.LazPaintInstance.ToolManager.BackFill.Texture.NewReference;
- end
- else Radio_UsePenColor.Checked := true;
- SpinEdit_Altitude.Value := FilterConnector.LazPaintInstance.Config.DefaultPhongFilterAltitude;
- with FilterConnector.LazPaintInstance.ToolManager.LightPosition do
- FCenter := PointF(X/FilterConnector.LazPaintInstance.Image.Width,
- Y/FilterConnector.LazPaintInstance.Image.Height);
- if Assigned(FilterConnector.Parameters) then
- with FilterConnector.Parameters do
- begin
- if IsDefined('ColorSource') then
- case Strings['ColorSource'] of
- 'Pen': Radio_UsePenColor.checked := true;
- 'Back': Radio_UseBackColor.checked := true;
- 'Layer': Radio_UseKeep.checked := true;
- end;
- if IsDefined('AltitudePercent') then
- SpinEdit_Altitude.Value := Integers['AltitudePercent'];
- if IsDefined('LightPosPercent') then
- FCenter := Points2D['LightPosPercent']*(1/100);
- if IsDefined('LightXPercent') then
- FCenter.x := Floats['LightXPercent']/100;
- if IsDefined('LightYPercent') then
- FCenter.y := Floats['LightYPercent']/100;
- if IsDefined('AltitudeSource') then
- case Strings['AltitudeSource'] of
- 'Lightness': Radio_MapLightness.Checked:= true;
- 'LinearLightness': Radio_MapLinearLightness.Checked:= true;
- 'Saturation': Radio_MapSaturation.Checked:= true;
- 'Alpha': Radio_MapAlpha.Checked:= true;
- 'Red': Radio_MapRed.Checked:= true;
- 'Green': Radio_MapGreen.Checked:= true;
- 'Blue': Radio_MapBlue.Checked:= true;
- end;
- end;
- SpinEdit_AltitudeChange(nil);
- FInitializing := false;
- end;
- procedure ScanLineMapLightness(psrc,pdest: PBGRAPixel; count: integer);
- const oneOver65535 = 1/65535;
- begin
- while count > 0 do
- begin
- pdest^ := MapHeightToBGRA(GetLightness(GammaExpansion(psrc^))*oneOver65535,psrc^.alpha);
- inc(pdest);
- inc(psrc);
- dec(count);
- end;
- end;
- procedure ScanLineMapLinearLightness(psrc,pdest: PBGRAPixel; count: integer);
- const oneOver255 = 1/255;
- begin
- while count > 0 do
- begin
- pdest^ := MapHeightToBGRA((psrc^.red*0.299+psrc^.green*0.587+psrc^.blue*0.114)*oneOver255,psrc^.alpha);
- inc(pdest);
- inc(psrc);
- dec(count);
- end;
- end;
- procedure ScanLineMapAlpha(psrc,pdest: PBGRAPixel; count: integer);
- begin
- while count > 0 do
- begin
- pdest^ := BGRA(psrc^.alpha,psrc^.alpha,psrc^.alpha,255);
- inc(pdest);
- inc(psrc);
- dec(count);
- end;
- end;
- procedure ScanLineMapBlue(psrc,pdest: PBGRAPixel; count: integer);
- begin
- while count > 0 do
- begin
- pdest^ := BGRA(psrc^.blue,psrc^.blue,psrc^.blue,psrc^.alpha);
- inc(pdest);
- inc(psrc);
- dec(count);
- end;
- end;
- procedure ScanLineMapGreen(psrc,pdest: PBGRAPixel; count: integer);
- begin
- while count > 0 do
- begin
- pdest^ := BGRA(psrc^.green,psrc^.green,psrc^.green,psrc^.alpha);
- inc(pdest);
- inc(psrc);
- dec(count);
- end;
- end;
- procedure ScanLineMapRed(psrc,pdest: PBGRAPixel; count: integer);
- begin
- while count > 0 do
- begin
- pdest^ := BGRA(psrc^.red,psrc^.red,psrc^.red,psrc^.alpha);
- inc(pdest);
- inc(psrc);
- dec(count);
- end;
- end;
- procedure ScanLineMapSaturation(psrc,pdest: PBGRAPixel; count: integer);
- const oneOver65535 = 1/65535;
- begin
- while count > 0 do
- begin
- with BGRAToHSLA(psrc^) do
- pdest^ := MapHeightToBGRA(saturation*oneOver65535,psrc^.alpha);
- inc(pdest);
- inc(psrc);
- dec(count);
- end;
- end;
- function TFPhongFilter.ComputeFilteredLayer: TBGRABitmap;
- var shader: TPhongShading;
- yb: integer;
- scanlineMapFunc: procedure(psrc,pdest: PBGRAPixel; count: integer);
- begin
- result := TBGRABitmap.Create(FilterConnector.ActiveLayer.Width, FilterConnector.ActiveLayer.Height);
- shader := TPhongShading.Create;
- shader.AmbientFactor := 0.5;
- shader.NegativeDiffusionFactor := 0.15;
- shader.LightPositionF := CurrentLightPos;
- shader.LightPositionZ := FilterConnector.LazPaintInstance.ToolManager.LightAltitude;
- if FHeightMap = nil then
- begin
- if Radio_MapLightness.Checked then
- scanlineMapFunc := @ScanLineMapLightness
- else if Radio_MapLinearLightness.Checked then
- scanlineMapFunc := @ScanLineMapLinearLightness
- else if Radio_MapAlpha.Checked then
- scanlineMapFunc := @ScanLineMapAlpha
- else if Radio_MapBlue.Checked then
- scanlineMapFunc := @ScanLineMapBlue
- else if Radio_MapGreen.Checked then
- scanlineMapFunc := @ScanLineMapGreen
- else if Radio_MapRed.Checked then
- scanlineMapFunc := @ScanLineMapRed
- else if Radio_MapSaturation.Checked then
- scanlineMapFunc := @ScanLineMapSaturation
- else
- scanlineMapFunc := nil;
- if Assigned(scanlineMapFunc) then
- begin
- FHeightMap := TBGRABitmap.Create(FilterConnector.BackupLayer.Width,FilterConnector.BackupLayer.Height);
- for yb := FilterConnector.WorkArea.Top to FilterConnector.WorkArea.Bottom-1 do
- scanlineMapFunc(FilterConnector.BackupLayer.ScanLine[yb]+FilterConnector.WorkArea.Left,
- FHeightMap.ScanLine[yb]+FilterConnector.WorkArea.Left, FilterConnector.WorkArea.Right - FilterConnector.WorkArea.Left);
- end;
- end;
- if FHeightMap <> nil then
- begin
- if Radio_UseTexture.Checked then
- shader.DrawScan(result, FHeightMap, SpinEdit_Altitude.Value, 0, 0, FTexture)
- else if Radio_UsePenColor.Checked then
- shader.Draw(result, FHeightMap, SpinEdit_Altitude.Value,0,0,FilterConnector.LazPaintInstance.ToolManager.ForeColor)
- else if Radio_UseKeep.Checked then
- shader.Draw(result, FHeightMap, SpinEdit_Altitude.Value,0,0,FilterConnector.BackupLayer)
- else
- shader.Draw(result, FHeightMap, SpinEdit_Altitude.Value,0,0,FilterConnector.LazPaintInstance.ToolManager.BackColor);
- end;
- shader.Free;
- end;
- {$R *.lfm}
- end.
|