From efda057bcc85c0d22c84a081bac7b37de839cfba Mon Sep 17 00:00:00 2001 From: mgaertner Date: Wed, 24 Apr 2024 14:12:06 +0000 Subject: [PATCH] added skia git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9338 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- components/skia/README.txt | 1 + .../skia/demo/console/ConsoleAppSkiaFPC.lpi | 71 + .../skia/demo/console/ConsoleAppSkiaFPC.lpr | 41 + .../skia/demo/paintbox/PaintControlSkLCL.lpi | 76 ++ .../skia/demo/paintbox/PaintControlSkLCL.lpr | 29 + .../skia/demo/paintbox/PaintControlSkLCL.res | Bin 0 -> 1824 bytes components/skia/demo/paintbox/powered_by.png | Bin 0 -> 15189 bytes components/skia/demo/paintbox/unit1.lfm | 10 + components/skia/demo/paintbox/unit1.pas | 118 ++ components/skia/design/Skia.LCL.Design.lpk | 39 + components/skia/design/SkiaLCLRegister.pas | 23 + .../skia/skia4d_package/Lazarus/Skia.lpk | 40 + .../skia/skia4d_package/Lazarus/Skia.pas | 21 + components/skia/src/LCL.Skia.pas | 1149 +++++++++++++++++ components/skia/src/LCL.SkiaInit.pas | 28 + components/skia/src/SkiaFPC.pas | 57 + components/skia/src/skia.lcl.lpk | 45 + components/skia/src/skia.lcl.pas | 21 + 18 files changed, 1769 insertions(+) create mode 100644 components/skia/README.txt create mode 100644 components/skia/demo/console/ConsoleAppSkiaFPC.lpi create mode 100644 components/skia/demo/console/ConsoleAppSkiaFPC.lpr create mode 100644 components/skia/demo/paintbox/PaintControlSkLCL.lpi create mode 100644 components/skia/demo/paintbox/PaintControlSkLCL.lpr create mode 100644 components/skia/demo/paintbox/PaintControlSkLCL.res create mode 100644 components/skia/demo/paintbox/powered_by.png create mode 100644 components/skia/demo/paintbox/unit1.lfm create mode 100644 components/skia/demo/paintbox/unit1.pas create mode 100644 components/skia/design/Skia.LCL.Design.lpk create mode 100644 components/skia/design/SkiaLCLRegister.pas create mode 100644 components/skia/skia4d_package/Lazarus/Skia.lpk create mode 100644 components/skia/skia4d_package/Lazarus/Skia.pas create mode 100644 components/skia/src/LCL.Skia.pas create mode 100644 components/skia/src/LCL.SkiaInit.pas create mode 100644 components/skia/src/SkiaFPC.pas create mode 100644 components/skia/src/skia.lcl.lpk create mode 100644 components/skia/src/skia.lcl.pas diff --git a/components/skia/README.txt b/components/skia/README.txt new file mode 100644 index 000000000..547dd751c --- /dev/null +++ b/components/skia/README.txt @@ -0,0 +1 @@ +see https://wiki.freepascal.org/Skia diff --git a/components/skia/demo/console/ConsoleAppSkiaFPC.lpi b/components/skia/demo/console/ConsoleAppSkiaFPC.lpi new file mode 100644 index 000000000..6a5f10577 --- /dev/null +++ b/components/skia/demo/console/ConsoleAppSkiaFPC.lpi @@ -0,0 +1,71 @@ + + + + + + + + + + + + + <UseAppBundle Value="False"/> + <ResourceType Value="res"/> + </General> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + </RunParams> + <RequiredPackages> + <Item> + <PackageName Value="Skia"/> + </Item> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="ConsoleAppSkiaFPC.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target> + <Filename Value="ConsoleAppSkiaFPC"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <AllowLabel Value="False"/> + </SyntaxOptions> + </Parsing> + <Linking> + <Debugging> + <DebugInfoType Value="dsDwarf2"/> + </Debugging> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions> + <Item> + <Name Value="EAbort"/> + </Item> + <Item> + <Name Value="ECodetoolError"/> + </Item> + <Item> + <Name Value="EFOpenError"/> + </Item> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/components/skia/demo/console/ConsoleAppSkiaFPC.lpr b/components/skia/demo/console/ConsoleAppSkiaFPC.lpr new file mode 100644 index 000000000..b7ebc545d --- /dev/null +++ b/components/skia/demo/console/ConsoleAppSkiaFPC.lpr @@ -0,0 +1,41 @@ +{ + Demo for a console app using the libsk4d library to create a surface, draw + on the canvas, and save the result as png file. + + Note: In skia4delphi 6.1 when the library is not found it silently aborts. +} +program ConsoleAppSkiaFPC; + +{$IFDEF MSWindows} + {$APPTYPE CONSOLE} +{$ENDIF} + +uses + {$IFDEF UNIX} + cthreads, + {$ENDIF} + dynlibs { dynlibs is needed when loading dynamic lib libsk4d }, + SysUtils, System.UITypes, + System.Skia; + +procedure TestSkSurface; +var + LSurface: ISkSurface; +begin + // Creating a solid red image using SkSurface + LSurface := TSkSurface.MakeRaster(200, 200); + LSurface.Canvas.Clear(TAlphaColors.Red); // $FFFF0000 + // create png file + LSurface.MakeImageSnapshot.EncodeToFile('test.png'); +end; + +begin + try + TestSkSurface; + WriteLn('Created test.png, <Enter>'); + ReadLn; + except + on E: Exception do + Writeln(E.ClassName, ': ', E.Message); + end; +end. diff --git a/components/skia/demo/paintbox/PaintControlSkLCL.lpi b/components/skia/demo/paintbox/PaintControlSkLCL.lpi new file mode 100644 index 000000000..92c67056c --- /dev/null +++ b/components/skia/demo/paintbox/PaintControlSkLCL.lpi @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectOptions> + <Version Value="12"/> + <General> + <SessionStorage Value="InProjectDir"/> + <Title Value="PaintControlSkLCL"/> + <Scaled Value="True"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <XPManifest> + <DpiAware Value="True"/> + </XPManifest> + </General> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + </RunParams> + <RequiredPackages> + <Item> + <PackageName Value="Skia.LCL"/> + </Item> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="PaintControlSkLCL.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="unit1.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="SkiaLCLPaintBoxDemo"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + <UnitName Value="Unit1"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target> + <Filename Value="PaintControlSkLCL"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <OtherUnitFiles Value="../../src/skia/skia4delphi"/> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions> + <Item> + <Name Value="EAbort"/> + </Item> + <Item> + <Name Value="ECodetoolError"/> + </Item> + <Item> + <Name Value="EFOpenError"/> + </Item> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/components/skia/demo/paintbox/PaintControlSkLCL.lpr b/components/skia/demo/paintbox/PaintControlSkLCL.lpr new file mode 100644 index 000000000..dcf6f1981 --- /dev/null +++ b/components/skia/demo/paintbox/PaintControlSkLCL.lpr @@ -0,0 +1,29 @@ +{ + Demo for the LCL TSkPaintBox +} +program PaintControlSkLCL; + +{$mode objfpc}{$H+} + +uses + {$IFDEF UNIX} + cthreads, + {$ENDIF} + {$IFDEF HASAMIGA} + athreads, + {$ENDIF} + Interfaces { this includes the LCL widgetset } + Forms, + LCL.SkiaInit { helper unit, to show an error if the libsk4d library was not found }, + unit1; + +{$R *.res} + +begin + RequireDerivedFormResource:=True; + Application.Scaled:=True; + Application.Initialize; + Application.CreateForm(TSkiaLCLPaintBoxDemo, SkiaLCLPaintBoxDemo); + Application.Run; +end. + diff --git a/components/skia/demo/paintbox/PaintControlSkLCL.res b/components/skia/demo/paintbox/PaintControlSkLCL.res new file mode 100644 index 0000000000000000000000000000000000000000..18ed76a9f75066926afa01c1beff825fdaec3b9a GIT binary patch literal 1824 zcmbVNU61256dhi8KtkF_9*rN7obRN`D4kWc0;z(9k!D!LqhsH6A`&OqPG>8`f90Pn zHy`b!tC<d>QW}3ApL6f=&DGV_6~X7zr|H4w+3%k(kMrN)nlHw=H*cPn<ZnMVHQ9qU zRjbz2cRWf!iB?u>x2C_||G-j8jO9x5x>c~I2Qc*Q&DUS$+!$zz`alpsnKkXTx;Ekg z8g5uq3Ei5uv`n<kH8+j3_bF+(s!A~S!}2tQA}6e^1Z69GI3whE1F2580*q<(MvI5a zg0Q_tI^R*Ec!Q01ZPRge_!&FSt!`y6?AYFP-SDQw9y(n1SgA0e?s%#MBXreSRDSb& z+iSwRuC4?h2FPO4$=yY7-r9jevf?mwNiJdP+sz!6arhZwOBOTp_s3v4zg0@MkH$eQ zO{>_uR$1NFhCYQO8G>;+99>_ah#%o_-~IuZ(j?B}B8mJc5~B2FNlDGsuIIZkdSfKW zAn+LhPs5YW-0I%gtt#99nDmfN;V~e3jd`7;qT$aTeE{-SSNp1l9hhl`<fp0cKjNMt z6F<QoYElpXYs$@5?NOJeq^~w&aKxIHyf!f96jR}zAU;FS-6f6OJ&764&e5nHX1=KE z!GEuM`fT~&?n&svL9^Cx86$IE$%hJW5@BqfGJV&z8pH6*9ofn?{R;xm_oH~l{5VQj z6pEPfxCG{TsRV#fhNbs6B~#8wzJy*~fDL0mOpA=OSY`-Y7Luh9NXCOG@`4~Pvyi_4 zn_hs8cu)o@ge(q=j73r8vw~-W$t05zk9Y!M`Vy?;UjX*YWR-|j!cq@>76oC((82{v zL+O=?^dw)s1nheka8vNg754*HltqYffRtfWViu*Pj6LqhJX^-W{XChudzSUlHdx#C z;q_wWWSybr?Za1#;_-e=C|H|@>wgE<V)Zpg6FykmUAyjFTb)yFb&!pF_kQaJo)^2b z$BvsPKVo6(s?8(UaC5JF!25jcj#bNj_T1UYqp<%^$xmLOG@MLw{Fv-+xqX<(jW;-V X&j-haukg%g35x_E{b6+aINkgUUmZOI literal 0 HcmV?d00001 diff --git a/components/skia/demo/paintbox/powered_by.png b/components/skia/demo/paintbox/powered_by.png new file mode 100644 index 0000000000000000000000000000000000000000..d065776c15a0b1069252ea22140db790ab19d4dc GIT binary patch literal 15189 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 E0kA-;{{R30 literal 0 HcmV?d00001 diff --git a/components/skia/demo/paintbox/unit1.lfm b/components/skia/demo/paintbox/unit1.lfm new file mode 100644 index 000000000..a140c09df --- /dev/null +++ b/components/skia/demo/paintbox/unit1.lfm @@ -0,0 +1,10 @@ +object SkiaLCLPaintBoxDemo: TSkiaLCLPaintBoxDemo + Left = 247 + Height = 301 + Top = 250 + Width = 374 + Caption = 'Skia LCL TSkPaintBox Demo' + OnCreate = FormCreate + OnDestroy = FormDestroy + LCLVersion = '3.99.0.0' +end diff --git a/components/skia/demo/paintbox/unit1.pas b/components/skia/demo/paintbox/unit1.pas new file mode 100644 index 000000000..b6a0a9569 --- /dev/null +++ b/components/skia/demo/paintbox/unit1.pas @@ -0,0 +1,118 @@ +{ + Demo for the LCL TSkPaintBox +} +unit Unit1; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, Types, Forms, Controls, Graphics, Dialogs, LCL.Skia, + System.Skia, system.UITypes; + +type + + { TSkiaLCLPaintBoxDemo } + + TSkiaLCLPaintBoxDemo = class(TForm) + procedure FormCreate(Sender: TObject); + procedure FormDestroy(Sender: TObject); + private + procedure OnSkPaintBoxDraw(Sender: TObject; const aCanvas: ISkCanvas; + const {%H-}aDest: TRectF; const {%H-}aOpacity: Single); + public + LogoPNG: ISkImage; + SkPaintBox: TSkPaintBox; + end; + +var + SkiaLCLPaintBoxDemo: TSkiaLCLPaintBoxDemo; + +implementation + +{$R *.lfm} + +{ TSkiaLCLPaintBoxDemo } + +procedure TSkiaLCLPaintBoxDemo.FormCreate(Sender: TObject); +var + ms: TMemoryStream; +begin + SkPaintBox:=TSkPaintBox.Create(Self); + with SkPaintBox do + begin + Name:='SkPaintBox'; + Align:=alClient; + OnDraw:=@OnSkPaintBoxDraw; + Parent:=Self; + end; + + ms:=TMemoryStream.Create; + try + ms.LoadFromFile('powered_by.png'); + ms.Position := 0; + LogoPNG := TSkImage.MakeFromEncodedStream(ms); + finally + ms.Free; + end; +end; + +procedure TSkiaLCLPaintBoxDemo.FormDestroy(Sender: TObject); +begin + LogoPNG:=nil; +end; + +procedure TSkiaLCLPaintBoxDemo.OnSkPaintBoxDraw(Sender: TObject; + const aCanvas: ISkCanvas; const aDest: TRectF; const aOpacity: Single); +var + SkPaint, SkPaint2: ISkPaint; + r: TRectF; + Oval: ISkRoundRect; + aPathBuilder: ISkPathBuilder; + aPath: ISkPath; + aTypeface: ISkTypeface; + aFont: ISkFont; + aTextBlob: ISkTextBlob; +begin + aCanvas.Clear(TAlphaColors.White); + + SkPaint:=TSkPaint.Create(TSkPaintStyle.Stroke); + SkPaint.SetAntiAlias(true); + SkPaint.setStrokeWidth(4); + SkPaint.setColor(TAlphaColors.Red); + r:=RectF(50, 50, 90, 110); + aCanvas.DrawRect(r, SkPaint); + + Oval:=TSkRoundRect.Create; + Oval.SetOval(r); + Oval.Offset(40,60); + SkPaint.setColor(TAlphaColors.Blue); + aCanvas.DrawRoundRect(Oval, SkPaint); + + SkPaint.setColor(TAlphaColors.Cyan); + aCanvas.DrawCircle(180, 50, 25, SkPaint); + + r.offset(80, 0); + SkPaint.setColor(TAlphaColors.Yellow); + aCanvas.DrawRoundRect(r, 10, 10, SkPaint); + + aPathBuilder:=TSkPathBuilder.Create; + aPathBuilder.cubicTo(768, 0, -512, 256, 256, 256); + aPath:=aPathBuilder.Detach; + SkPaint.setColor(TAlphaColors.Lime); + aCanvas.DrawPath(aPath, SkPaint); + + aCanvas.DrawImage(LogoPNG, 128, 128); + + aTypeface := TSkTypeface.MakeFromName('Monospace', TSkFontStyle.Normal); + aFont := TSkFont.Create(aTypeface, 18, 1); + aFont.Edging := TSkFontEdging.AntiAlias; + + SkPaint2:=TSkPaint.Create; + aTextBlob:=TSkTextBlob.MakeFromText('Hello, Skia!',aFont); + aCanvas.DrawTextBlob(aTextBlob, 50, 25, SkPaint2); +end; + +end. + diff --git a/components/skia/design/Skia.LCL.Design.lpk b/components/skia/design/Skia.LCL.Design.lpk new file mode 100644 index 000000000..a1895ea8a --- /dev/null +++ b/components/skia/design/Skia.LCL.Design.lpk @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <Package Version="5"> + <Name Value="Skia.LCL.Design"/> + <Type Value="RunAndDesignTime"/> + <Author Value="Mattias Gaertner"/> + <CompilerOptions> + <Version Value="11"/> + <SearchPaths> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)/"/> + </SearchPaths> + </CompilerOptions> + <Description Value="IDE addon to install LCL components for Skia in Lazarus, requires FPC 3.3.1+"/> + <License Value="modified LGPL-2"/> + <Version Major="1"/> + <Files> + <Item> + <Filename Value="SkiaLCLRegister.pas"/> + <HasRegisterProc Value="True"/> + <UnitName Value="SkiaLCLRegister"/> + </Item> + </Files> + <RequiredPkgs> + <Item> + <PackageName Value="Skia.LCL"/> + </Item> + <Item> + <PackageName Value="FCL"/> + </Item> + </RequiredPkgs> + <UsageOptions> + <UnitPath Value="$(PkgOutDir)"/> + </UsageOptions> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + </Package> +</CONFIG> diff --git a/components/skia/design/SkiaLCLRegister.pas b/components/skia/design/SkiaLCLRegister.pas new file mode 100644 index 000000000..19bbe7af8 --- /dev/null +++ b/components/skia/design/SkiaLCLRegister.pas @@ -0,0 +1,23 @@ +unit SkiaLCLRegister; + +{$mode objfpc}{$H+} +{$IF FPC_FULLVERSION<30301} + {$error requires at least fpc 3.3.1} +{$ENDIF} + +interface + +uses + LCL.Skia; + +procedure Register; + +implementation + +procedure Register; +begin + RegisterComponents('Skia', [TSkPaintBox]); +end; + +end. + diff --git a/components/skia/skia4d_package/Lazarus/Skia.lpk b/components/skia/skia4d_package/Lazarus/Skia.lpk new file mode 100644 index 000000000..b52de1627 --- /dev/null +++ b/components/skia/skia4d_package/Lazarus/Skia.lpk @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <Package Version="5"> + <Name Value="Skia"/> + <Type Value="RunAndDesignTime"/> + <Author Value="Skia4Delphi Project"/> + <CompilerOptions> + <Version Value="11"/> + <SearchPaths> + <OtherUnitFiles Value="../../Source"/> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)/"/> + </SearchPaths> + </CompilerOptions> + <Description Value="Skia API, requires FPC 3.3.1+"/> + <License Value="BSD-style, see LICENSE file"/> + <Version Major="1"/> + <Files> + <Item> + <Filename Value="../../Source/System.Skia.API.pas"/> + <UnitName Value="System.Skia.API"/> + </Item> + <Item> + <Filename Value="../../Source/System.Skia.pas"/> + <UnitName Value="System.Skia"/> + </Item> + </Files> + <RequiredPkgs> + <Item> + <PackageName Value="FCL"/> + </Item> + </RequiredPkgs> + <UsageOptions> + <UnitPath Value="$(PkgOutDir)"/> + </UsageOptions> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + </Package> +</CONFIG> diff --git a/components/skia/skia4d_package/Lazarus/Skia.pas b/components/skia/skia4d_package/Lazarus/Skia.pas new file mode 100644 index 000000000..5ccd43692 --- /dev/null +++ b/components/skia/skia4d_package/Lazarus/Skia.pas @@ -0,0 +1,21 @@ +{ This file was automatically created by Lazarus. Do not edit! + This source is only used to compile and install the package. + } + +unit Skia; + +{$warn 5023 off : no warning about unused units} +interface + +uses + System.Skia.API, System.Skia, LazarusPackageIntf; + +implementation + +procedure Register; +begin +end; + +initialization + RegisterPackage('Skia', @Register); +end. diff --git a/components/skia/src/LCL.Skia.pas b/components/skia/src/LCL.Skia.pas new file mode 100644 index 000000000..ff13c19a6 --- /dev/null +++ b/components/skia/src/LCL.Skia.pas @@ -0,0 +1,1149 @@ +unit LCL.Skia; + +{$Mode ObjFPC}{$H+} +{$ScopedEnums On} +{$IF FPC_FULLVERSION<30301} + {$error requires at least fpc 3.3.1} +{$ENDIF} + +interface + +uses + Classes, SysUtils, Math, Types, System.UITypes, Controls, IntfGraphics, + Graphics, LCLIntf, GraphType, System.Skia, SkiaFPC; + +type + ESkLcl = class(Exception); + + TSkTextHorzAlign = (Center, Leading, Trailing, Justify); + TSkTextVertAlign = (Center, Leading, Trailing); + TSkTextTrimming = (None, Character, Word); + TSkStyledSetting = (Family, Size, Style, FontColor, Other); + TSkStyledSettings = set of TSkStyledSetting; + + TSkDrawEvent = procedure(Sender: TObject; const aCanvas: ISkCanvas; + const aDest: TRectF; const aOpacity: Single) of object; + TSkDrawCacheKind = (Never, Raster, Always); + + { TSkCustomControl } + + TSkCustomControl = class (TGraphicControl) + private + FDrawCached: Boolean; + FDrawCacheKind: TSkDrawCacheKind; + FIntfImg: TLazIntfImage; + FOnDraw: TSkDrawEvent; + FOpacity: Byte; + procedure SetDrawCacheKind(const AValue: TSkDrawCacheKind); + procedure SetOnDraw(const AValue: TSkDrawEvent); + procedure SetOpacity(const AValue: Byte); + protected + FScaleFactor: Single; + procedure ChangeScale(Multiplier, Divider: Integer); override; + procedure Draw(const aCanvas: ISkCanvas; const aDest: TRectF; const aOpacity: Single); virtual; + procedure DeleteBuffers; virtual; + function NeedsRedraw: Boolean; virtual; + procedure Paint; override; + procedure Resize; override; + property DrawCacheKind: TSkDrawCacheKind read FDrawCacheKind write SetDrawCacheKind default TSkDrawCacheKind.Raster; + property OnDraw: TSkDrawEvent read FOnDraw write SetOnDraw; + public + class function GetControlClassDefaultSize: TSize; override; + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + procedure Redraw; virtual; + procedure Invalidate; override; + property Opacity: Byte read FOpacity write SetOpacity default 255; + property ScaleFactor: Single read FScaleFactor; + end; + + { TSkPaintBox } + + TSkPaintBox = class(TSkCustomControl) + public + property DrawCacheKind; + published + property Align; + property Anchors; + property BorderSpacing; + property Color; + property Constraints; + property DragCursor; + property DragMode; + property Enabled; + property Font; + property Hint; + property ParentColor; + property ParentFont; + property ParentShowHint; + property PopupMenu; + property ShowHint; + property Visible; + property OnChangeBounds; + property OnClick; + property OnContextPopup; + property OnDblClick; + property OnDragDrop; + property OnDragOver; + property OnDraw; + property OnEndDrag; + property OnMouseDown; + property OnMouseEnter; + property OnMouseLeave; + property OnMouseMove; + property OnMouseUp; + property OnMouseWheel; + property OnMouseWheelDown; + property OnMouseWheelUp; + property OnMouseWheelHorz; + property OnMouseWheelLeft; + property OnMouseWheelRight; + property OnPaint; + property OnResize; + property OnStartDrag; + end; + + TSkSvgSource = type string; + TSkSvgWrapMode = (Default, Fit, FitCrop, Original, OriginalCenter, Place, Stretch, Tile); + + { TSkSvgBrush } + + TSkSvgBrush = class(TPersistent) + strict private const + DefaultGrayScale = False; + DefaultWrapMode = TSkSvgWrapMode.Fit; + strict private + FDOM: ISkSVGDOM; + FGrayScale: Boolean; + FOnChanged: TNotifyEvent; + FOriginalSize: TSizeF; + FOverrideColor: TAlphaColor; + FSource: TSkSvgSource; + FWrapMode: TSkSvgWrapMode; + function GetDOM: ISkSVGDOM; + function GetOriginalSize: TSizeF; + procedure SetGrayScale(const AValue: Boolean); + procedure SetOverrideColor(const AValue: TAlphaColor); + procedure SetSource(const AValue: TSkSvgSource); + procedure SetWrapMode(const AValue: TSkSvgWrapMode); + strict protected + procedure DoAssign(ASource: TSkSvgBrush); virtual; + procedure DoChanged; virtual; + function HasContent: Boolean; virtual; + function MakeDOM: ISkSVGDOM; virtual; + procedure RecreateDOM; + public + constructor Create; + procedure Assign(ASource: TPersistent); override; + function Equals(AObject: TObject): Boolean; override; + procedure Render(const ACanvas: ISkCanvas; const ADestRect: TRectF; const AOpacity: Single); + property DOM: ISkSVGDOM read GetDOM; + property OriginalSize: TSizeF read GetOriginalSize; + property OnChanged: TNotifyEvent read FOnChanged write FOnChanged; + published + property GrayScale: Boolean read FGrayScale write SetGrayScale default DefaultGrayScale; + property OverrideColor: TAlphaColor read FOverrideColor write SetOverrideColor; + property Source: TSkSvgSource read FSource write SetSource; + property WrapMode: TSkSvgWrapMode read FWrapMode write SetWrapMode default DefaultWrapMode; + end; + + { TSkSvg } + + TSkSvg = class(TSkCustomControl) + strict private + FSvg: TSkSvgBrush; + procedure SetSvg(const AValue: TSkSvgBrush); + procedure SvgChanged({%H-}ASender: TObject); + strict protected + function CreateSvgBrush: TSkSvgBrush; virtual; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + procedure Draw(const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); override; + published + property Svg: TSkSvgBrush read FSvg write SetSvg; + property OnDraw; + end; + + TSkControlRenderBackend = (Default, Raster, HardwareAcceleration); + + { ISkControlRenderTarget } + + ISkControlRenderTarget = interface + ['{DBEF2E70-E032-4B70-BDF9-C3DCAED502C7}'] + procedure Draw(const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); + function GetCanvas: TCanvas; + //function GetDeviceContext(var WindowHandle: HWND): HDC; + function GetDrawCacheKind: TSkDrawCacheKind; + function GetClientHeight: Integer; + function GetScaleFactor: Single; + function GetClientWidth: Integer; + property Canvas: TCanvas read GetCanvas; + property DrawCacheKind: TSkDrawCacheKind read GetDrawCacheKind; + property ClientHeight: Integer read GetClientHeight; + property ScaleFactor: Single read GetScaleFactor; + property ClientWidth: Integer read GetClientWidth; + end; + + { ISkControlRender } + + ISkControlRender = interface + ['{6ACA5428-9554-4CB8-8644-4D796B9D8333}'] + procedure Redraw; + procedure Resized; + function TryRender(const ABackgroundBuffer: TBitmap; const AOpacity: Byte): Boolean; + end; + + { TSkControlRender } + + TSkControlRender = class abstract(TInterfacedObject) + public + class function MakeRender(const ATarget: ISkControlRenderTarget; const ABackend: TSkControlRenderBackend): ISkControlRender; static; + end; + + { TSkCustomWinControl - base class for your custom controls } + + TSkCustomWinControl = class abstract(TCustomControl, ISkControlRenderTarget) + private + FBackendRender: TSkControlRenderBackend; + FBackgroundColor: TAlphaColor; + FDrawCacheKind: TSkDrawCacheKind; + FOnDraw: TSkDrawEvent; + FRender: ISkControlRender; + FScaleFactor: Single; + function GetRender: ISkControlRender; + procedure DeleteBuffers; + procedure SetBackendRender(AValue: TSkControlRenderBackend); + procedure SetBackgroundColor(AValue: TAlphaColor); + procedure SetDrawCacheKind(AValue: TSkDrawCacheKind); + procedure SetOnDraw(AValue: TSkDrawEvent); + protected + procedure DestroyWnd; override; // called after child handles are freed + procedure DrawContent(const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); virtual; + function MakeRender(ABackendRender: TSkControlRenderBackend): ISkControlRender; virtual; + procedure Paint; override; + procedure Resize; override; + procedure ChangeScale(Multiplier, Divider: Integer); override; + class function GetControlClassDefaultSize: TSize; override; + // for interface ISkControlRenderTarget: + procedure Draw(const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); virtual; + function GetCanvas: TCanvas; virtual; + function GetDrawCacheKind: TSkDrawCacheKind; virtual; + function GetClientHeight: Integer; virtual; + function GetScaleFactor: Single; virtual; + function GetClientWidth: Integer; virtual; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + property BackendRender: TSkControlRenderBackend read FBackendRender write SetBackendRender default TSkControlRenderBackend.Default; + property BackgroundColor: TAlphaColor read FBackgroundColor write SetBackgroundColor default TAlphaColors.White; + property DrawCacheKind: TSkDrawCacheKind read FDrawCacheKind write SetDrawCacheKind default TSkDrawCacheKind.Raster; + property OnDraw: TSkDrawEvent read FOnDraw write SetOnDraw; + property Render: ISkControlRender read GetRender; + property ScaleFactor: Single read FScaleFactor; + end; + + { TSkDefaultProviders } + + TSkDefaultProviders = class + strict private class var + FResource: ISkResourceProvider; + FTypefaceFont: ISkTypefaceFontProvider; + class constructor Create; + public + class procedure RegisterTypeface(const AFileName: string); overload; static; + class procedure RegisterTypeface(const AStream: TStream); overload; static; + class property Resource: ISkResourceProvider read FResource write FResource; + class property TypefaceFont: ISkTypefaceFontProvider read FTypefaceFont; + end; + +function BitmapToSkImage(const ABitmap: TBitmap): ISkImage; +procedure DrawDesignBorder(const ACanvas: ISkCanvas; ADest: TRectF; const AOpacity: Single); +//procedure SkiaDraw(const ABitmap: TBitmap; const AProc: TSkDrawProc; const AStartClean: Boolean = True); +function SkImageToBitmap(const AImage: ISkImage): TBitmap; + +const + AllStyledSettings: TSkStyledSettings = [TSkStyledSetting.Family, TSkStyledSetting.Size, + TSkStyledSetting.Style, TSkStyledSetting.FontColor, TSkStyledSetting.Other]; + DefaultStyledSettings: TSkStyledSettings = [TSkStyledSetting.Family, TSkStyledSetting.Size, + TSkStyledSetting.Style, TSkStyledSetting.FontColor]; + +implementation + +procedure FlipPixelsVertically(IntfImg: TLazIntfImage); +var + I, J, Height: Integer; + Stride: PtrUInt; + Row, Pixels: PByte; +begin + Stride:=IntfImg.DataDescription.BytesPerLine; + Row:=GetMem(Stride); + try + Pixels:=IntfImg.PixelData; + Height:=IntfImg.Height; + for I := 0 to Height div 2 -1 do + begin + J:=Height-I-1; + Move(Pixels[I * Stride], Row^, Stride); + Move(Pixels[J * Stride], Pixels[I * Stride], Stride); + Move(Row^, Pixels[J * Stride], Stride); + end; + finally + FreeMem(Row); + end; +end; + +procedure BitmapToSkImage_Release(const APixels: Pointer); +begin + FreeMem(APixels); +end; + +function BitmapToSkImage(const ABitmap: TBitmap): ISkImage; +var + IntfImg: TLazIntfImage; + LAlphaType: TSkAlphaType; + LStream: TMemoryStream; + Height: Integer; +begin + if ABitmap.Empty then + raise ESkLcl.Create('Invalid bitmap'); + + if ABitmap.PixelFormat = TPixelFormat.pf32bit then + begin + IntfImg:=ABitmap.CreateIntfImage; + try + Height:=IntfImg.Height; + //case ABitmap.AlphaFormat of + // TAlphaFormat.afIgnored: LAlphaType := TSkAlphaType.Opaque; + // TAlphaFormat.afDefined: LAlphaType := TSkAlphaType.Unpremul; + // TAlphaFormat.afPremultiplied: LAlphaType := TSkAlphaType.Premul; + //else + LAlphaType := TSkAlphaType.Unknown; + //end; + if IntfImg.DataDescription.LineOrder=riloTopToBottom then + FlipPixelsVertically(IntfImg); + Result := TSkImage.MakeFromRaster( + TSkImageInfo.Create(IntfImg.Width, Height, SkNative32ColorType, LAlphaType), + IntfImg.PixelData, + IntfImg.DataDescription.BytesPerLine, + @BitmapToSkImage_Release); + finally + IntfImg.Free; + end; + end + else begin + LStream := TMemoryStream.Create; + try + ABitmap.SaveToStream(LStream); + LStream.Position := 0; + Result := TSkImage.MakeFromEncodedStream(LStream); + finally + LStream.Free; + end; + end; +end; + +procedure DrawDesignBorder(const ACanvas: ISkCanvas; ADest: TRectF; + const AOpacity: Single); +const + DesignBorderColor = $A0909090; +var + LPaint: ISkPaint; +begin + LPaint := TSkPaint.Create(TSkPaintStyle.Stroke); + LPaint.AlphaF := AOpacity; + LPaint.Color := DesignBorderColor; + LPaint.StrokeWidth := 1; + LPaint.PathEffect := TSkPathEffect.MakeDash([3, 1], 0); + + ADest.Inflate(-0.5, -0.5); + ACanvas.DrawRect(ADest, LPaint); +end; + +function SkImageToBitmap(const AImage: ISkImage): TBitmap; +var + IntfImg: TLazIntfImage; +begin + Assert(Assigned(AImage)); + Result:=TBitMap.Create; + Result.PixelFormat := TPixelFormat.pf32bit; + //Result.AlphaFormat := TAlphaFormat.afPremultiplied; + if (AImage.Width=0) and (AImage.Height=0) then exit; + + IntfImg:=TLazIntfImage.Create(0,0); + try + if SkNative32ColorType=TSkColorType.BGRA8888 then + IntfImg.DataDescription.Init_BPP32_B8G8R8A8_BIO_TTB(AImage.Width,AImage.Height) + else + IntfImg.DataDescription.Init_BPP32_A8R8G8B8_BIO_TTB(AImage.Width,AImage.Height); + + AImage.ReadPixels(TSkImageInfo.Create(AImage.Width, AImage.Height), + IntfImg.PixelData, IntfImg.DataDescription.BytesPerLine); + FlipPixelsVertically(IntfImg); + + Result.LoadFromIntfImage(IntfImg); + finally + IntfImg.Free; + end; +end; + +function PlaceIntoTopLeft(const ASourceRect, ADesignatedArea: TRectF): TRectF; +begin + Result := ASourceRect; + if (ASourceRect.Width > ADesignatedArea.Width) or (ASourceRect.Height > ADesignatedArea.Height) then + Result := Result.FitInto(ADesignatedArea); + Result.SetLocation(ADesignatedArea.TopLeft); +end; + +{ TSkCustomControl } + +procedure TSkCustomControl.SetOpacity(const AValue: Byte); +begin + if FOpacity=AValue then Exit; + FOpacity:=AValue; + Invalidate; +end; + +procedure TSkCustomControl.ChangeScale(Multiplier, Divider: Integer); +begin + if Multiplier <> Divider then + FScaleFactor := FScaleFactor * Multiplier / Divider; + inherited ChangeScale(Multiplier, Divider); +end; + +procedure TSkCustomControl.SetOnDraw(const AValue: TSkDrawEvent); +begin + if FOnDraw=AValue then Exit; + FOnDraw:=AValue; + Invalidate; +end; + +procedure TSkCustomControl.SetDrawCacheKind(const AValue: TSkDrawCacheKind); +begin + if FDrawCacheKind=AValue then Exit; + FDrawCacheKind:=AValue; + if FDrawCacheKind <> TSkDrawCacheKind.Always then + Invalidate; +end; + +procedure TSkCustomControl.Draw(const aCanvas: ISkCanvas; const aDest: TRectF; + const aOpacity: Single); +begin + if csDesigning in ComponentState then + DrawDesignBorder(ACanvas, ADest, AOpacity); +end; + +procedure TSkCustomControl.DeleteBuffers; +begin + if FIntfImg<>nil then + begin + FDrawCached := False; + FreeAndNil(FIntfImg); + end; +end; + +function TSkCustomControl.NeedsRedraw: Boolean; +begin + Result := (not FDrawCached) + or (FDrawCacheKind = TSkDrawCacheKind.Never) + or (FIntfImg = nil); +end; + +procedure TSkCustomControl.Paint; + + procedure InternalDraw; + var + LSurface: ISkSurface; + LDestRect: TRectF; + begin + LSurface := TSkSurface.MakeRasterDirect(TSkImageInfo.Create(Width, Height), + FIntfImg.PixelData, FIntfImg.DataDescription.BytesPerLine); + LSurface.Canvas.Clear(TAlphaColors.Null); + LSurface.Canvas.Concat(TMatrix.CreateScaling(ScaleFactor, ScaleFactor)); + LDestRect := RectF(0, 0, Single(Width) / ScaleFactor, Single(Height) / ScaleFactor); + Draw(LSurface.Canvas, LDestRect, 1); + if Assigned(OnDraw) then + OnDraw(Self, LSurface.Canvas, LDestRect, 1); + FDrawCached := True; + end; + +var + Desc: TRawImageDescription; + Bmp: TBitmap; +begin + if (Width <= 0) or (Height <= 0) then + Exit; + + if FIntfImg=nil then + begin + if SkNative32ColorType=TSkColorType.BGRA8888 then + Desc.Init_BPP32_B8G8R8A8_BIO_TTB(Width, Height) + else + Desc.Init_BPP32_R8G8B8A8_BIO_TTB(Width, Height); + FIntfImg:=TLazIntfImage.Create(0,0); + FIntfImg.DataDescription:=Desc; + FIntfImg.SetSize(Width,Height); + end; + + if NeedsRedraw then + InternalDraw; + + Bmp:=TBitmap.Create; + try + Bmp.LoadFromIntfImage(FIntfImg); + Canvas.Draw(0,0,Bmp); + finally + Bmp.Free; + end; + + inherited Paint; +end; + +procedure TSkCustomControl.Resize; +begin + DeleteBuffers; + inherited Resize; +end; + +class function TSkCustomControl.GetControlClassDefaultSize: TSize; +begin + Result.CX := 50; + Result.CY := 50; +end; + +constructor TSkCustomControl.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + ControlStyle := ControlStyle + [csReplicatable] - [csOpaque]; + FDrawCacheKind := TSkDrawCacheKind.Raster; + FOpacity := 255; + FScaleFactor := 1; +end; + +destructor TSkCustomControl.Destroy; +begin + DeleteBuffers; + inherited Destroy; +end; + +procedure TSkCustomControl.Redraw; +begin + FDrawCached := False; + Repaint; +end; + +procedure TSkCustomControl.Invalidate; +begin + FDrawCached := false; + inherited Invalidate; +end; + +{ TSkSvgBrush } + +function TSkSvgBrush.GetDOM: ISkSVGDOM; +var + LSvgRect: TRectF; +begin + if (FDOM = nil) and HasContent then + begin + FDOM := MakeDOM; + if Assigned(FDOM) then + begin + LSvgRect.TopLeft := PointF(0, 0); + LSvgRect.Size := FDOM.Root.GetIntrinsicSize(TSizeF.Create(0, 0)); + if (not LSvgRect.IsEmpty) or (FDOM.Root.TryGetViewBox(LSvgRect) and not LSvgRect.IsEmpty) then + FOriginalSize := LSvgRect.Size; + end; + end; + Result := FDOM; +end; + +function TSkSvgBrush.GetOriginalSize: TSizeF; +begin + if (FDOM = nil) and HasContent then + GetDOM; + Result := FOriginalSize; +end; + +procedure TSkSvgBrush.SetGrayScale(const AValue: Boolean); +begin + if FGrayScale <> AValue then + begin + FGrayScale := AValue; + if HasContent then + DoChanged; + end; +end; + +procedure TSkSvgBrush.SetOverrideColor(const AValue: TAlphaColor); +begin + if FOverrideColor <> AValue then + begin + FOverrideColor := AValue; + if HasContent then + DoChanged; + end; +end; + +procedure TSkSvgBrush.SetSource(const AValue: TSkSvgSource); +begin + if FSource <> AValue then + begin + FSource := AValue; + RecreateDOM; + DoChanged; + end; +end; + +procedure TSkSvgBrush.SetWrapMode(const AValue: TSkSvgWrapMode); +begin + if FWrapMode <> AValue then + begin + FWrapMode := AValue; + RecreateDOM; + if HasContent then + DoChanged; + end; +end; + +procedure TSkSvgBrush.DoAssign(ASource: TSkSvgBrush); +begin + FDOM := ASource.FDOM; + FGrayScale := ASource.FGrayScale; + FOriginalSize := ASource.FOriginalSize; + FOverrideColor := ASource.FOverrideColor; + FSource := ASource.FSource; + FWrapMode := ASource.FWrapMode; +end; + +procedure TSkSvgBrush.DoChanged; +begin + if Assigned(FOnChanged) then + FOnChanged(Self); +end; + +function TSkSvgBrush.HasContent: Boolean; +begin + Result := FSource <> ''; +end; + +function TSkSvgBrush.MakeDOM: ISkSVGDOM; +begin + Result := TSkSVGDOM.Make(UnicodeString(FSource), TSkDefaultProviders.Resource); +end; + +procedure TSkSvgBrush.RecreateDOM; +begin + FDOM := nil; + FOriginalSize := TSizeF.Create(0, 0); +end; + +constructor TSkSvgBrush.Create; +begin + inherited Create; + FGrayScale := DefaultGrayScale; + FWrapMode := DefaultWrapMode; +end; + +procedure TSkSvgBrush.Assign(ASource: TPersistent); +var + LSourceSvgBrush: TSkSvgBrush absolute ASource; +begin + if ASource is TSkSvgBrush then + begin + if not Equals(LSourceSvgBrush) then + begin + DoAssign(LSourceSvgBrush); + DoChanged; + end; + end + else + inherited; +end; + +function TSkSvgBrush.Equals(AObject: TObject): Boolean; +var + LObjectSvgBrush: TSkSvgBrush absolute AObject; +begin + Result := (AObject is TSkSvgBrush) and + (FGrayScale = LObjectSvgBrush.FGrayScale) and + (FOverrideColor = LObjectSvgBrush.FOverrideColor) and + (FWrapMode = LObjectSvgBrush.FWrapMode) and + (FSource = LObjectSvgBrush.FSource); +end; + +procedure TSkSvgBrush.Render(const ACanvas: ISkCanvas; const ADestRect: TRectF; + const AOpacity: Single); + + function GetWrappedDest(const ADOM: ISkSVGDOM; const ASvgRect, ADestRect: TRectF; + const AIntrinsicSize: TSizeF): TRectF; + var + LRatio: Single; + begin + case FWrapMode of + TSkSvgWrapMode.Default: + begin + if AIntrinsicSize.IsZero then + Result := ADestRect + else + begin + Result := ASvgRect; + Result.Offset(ADestRect.TopLeft); + end; + ADOM.SetContainerSize(ADestRect.Size); + end; + TSkSvgWrapMode.Fit: Result := ASvgRect.FitInto(ADestRect); + TSkSvgWrapMode.FitCrop: + begin + if (ASvgRect.Width / ADestRect.Width) < (ASvgRect.Height / ADestRect.Height) then + LRatio := ASvgRect.Width / ADestRect.Width + else + LRatio := ASvgRect.Height / ADestRect.Height; + if SameValue(LRatio, 0, TEpsilon.Vector) then + Result := ADestRect + else + begin + Result := RectF(0, 0, Round(ASvgRect.Width / LRatio), Round(ASvgRect.Height / LRatio)); + RectCenter(Result, ADestRect); + end; + end; + TSkSvgWrapMode.Original, + TSkSvgWrapMode.Tile: Result := ASvgRect; + TSkSvgWrapMode.OriginalCenter: + begin + Result := ASvgRect; + RectCenter(Result, ADestRect); + end; + TSkSvgWrapMode.Place: Result := PlaceIntoTopLeft(ASvgRect, ADestRect); + TSkSvgWrapMode.Stretch: Result := ADestRect; + else + Result := ADestRect{%H-}; + end; + end; + + procedure DrawTileOrCustomColor(const ACanvas: ISkCanvas; const ADOM: ISkSVGDOM; + const ASvgRect, ADestRect, AWrappedDest: TRectF; const AIntrinsicSize: TSizeF; + const AWrapMode: TSkSvgWrapMode); + var + LPicture: ISkPicture; + LPictureRecorder: ISkPictureRecorder; + LCanvas: ISkCanvas; + LPaint: ISkPaint; + begin + LPictureRecorder := TSkPictureRecorder.Create; + LCanvas := LPictureRecorder.BeginRecording(AWrappedDest.Width, AWrappedDest.Height); + if AIntrinsicSize.IsZero then + begin + if AWrapMode <> TSkSvgWrapMode.Default then + begin + ADOM.Root.Width := TSkSVGLength.Create(AWrappedDest.Width, TSkSVGLengthUnit.Pixel); + ADOM.Root.Height := TSkSVGLength.Create(AWrappedDest.Height, TSkSVGLengthUnit.Pixel); + end; + end + else + LCanvas.Scale(AWrappedDest.Width / ASvgRect.Width, AWrappedDest.Height / ASvgRect.Height); + ADOM.Render(LCanvas); + LPicture := LPictureRecorder.FinishRecording; + LPaint := TSkPaint.Create; + if FGrayScale then + LPaint.ColorFilter := TSkColorFilter.MakeMatrix(TSkColorMatrix.CreateSaturation(0)) + else if FOverrideColor <> TAlphaColors.Null then + LPaint.ColorFilter := TSkColorFilter.MakeBlend(FOverrideColor, TSkBlendMode.SrcIn); + if FWrapMode = TSkSvgWrapMode.Tile then + begin + LPaint.Shader := LPicture.MakeShader(TSkTileMode.&Repeat, TSkTileMode.&Repeat); + ACanvas.DrawRect(ADestRect, LPaint); + end + else + begin + ACanvas.Translate(AWrappedDest.Left, AWrappedDest.Top); + ACanvas.DrawPicture(LPicture, LPaint); + end; + end; + +var + LDOM: ISkSVGDOM; + LSvgRect: TRectF; + LWrappedDest: TRectF; + LIntrinsicSize: TSizeF; +begin + if not ADestRect.IsEmpty then + begin + LDOM := DOM; + if Assigned(LDOM) then + begin + LSvgRect.TopLeft := PointF(0, 0); + LIntrinsicSize := LDOM.Root.GetIntrinsicSize(TSizeF.Create(0, 0)); + LSvgRect.Size := LIntrinsicSize; + if LSvgRect.IsEmpty and ((not LDOM.Root.TryGetViewBox(LSvgRect)) or LSvgRect.IsEmpty) then + Exit; + + if SameValue(AOpacity, 1, TEpsilon.Position) then + ACanvas.Save + else + ACanvas.SaveLayerAlpha(Round(AOpacity * 255)); + try + LWrappedDest := GetWrappedDest(LDOM, LSvgRect, ADestRect, LIntrinsicSize); + if (FOverrideColor <> TAlphaColors.Null) or (FWrapMode = TSkSvgWrapMode.Tile) or FGrayScale then + DrawTileOrCustomColor(ACanvas, LDOM, LSvgRect, ADestRect, LWrappedDest, LIntrinsicSize, FWrapMode) + else + begin + ACanvas.Translate(LWrappedDest.Left, LWrappedDest.Top); + if LIntrinsicSize.IsZero then + begin + if FWrapMode <> TSkSvgWrapMode.Default then + begin + LDOM.Root.Width := TSkSVGLength.Create(LWrappedDest.Width, TSkSVGLengthUnit.Pixel); + LDOM.Root.Height := TSkSVGLength.Create(LWrappedDest.Height, TSkSVGLengthUnit.Pixel); + end; + end + else + ACanvas.Scale(LWrappedDest.Width / LSvgRect.Width, LWrappedDest.Height / LSvgRect.Height); + LDOM.Render(ACanvas); + end; + finally + ACanvas.Restore; + end; + end; + end; +end; + +{ TSkSvg } + +procedure TSkSvg.SetSvg(const AValue: TSkSvgBrush); +begin + FSvg.Assign(AValue); +end; + +procedure TSkSvg.SvgChanged(ASender: TObject); +begin + Redraw; +end; + +function TSkSvg.CreateSvgBrush: TSkSvgBrush; +begin + Result := TSkSvgBrush.Create; +end; + +procedure TSkSvg.Draw(const ACanvas: ISkCanvas; const ADest: TRectF; + const AOpacity: Single); +begin + inherited Draw(ACanvas, ADest, AOpacity); + FSvg.Render(ACanvas, ADest, AOpacity); +end; + +constructor TSkSvg.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + FSvg := CreateSvgBrush; + FSvg.OnChanged := @SvgChanged; + DrawCacheKind := TSkDrawCacheKind.Always; +end; + +destructor TSkSvg.Destroy; +begin + FreeAndNil(FSvg); + inherited Destroy; +end; + +type + { TSkRasterControlRender } + + TSkRasterControlRender = class(TSkControlRender, ISkControlRender) + strict private + FDrawCached: Boolean; + FIntfImg: TLazIntfImage; + FTarget: ISkControlRenderTarget; + procedure DeleteBuffers; + procedure DoRender(const AWidth, AHeight: Integer; const AScaleFactor: Single; + const ACanvas: TCanvas{; const ABackgroundBuffer: TBitmap; const AOpacity: Byte}); + public + constructor Create(const ATarget: ISkControlRenderTarget); + destructor Destroy; override; + procedure Redraw; + procedure Resized; + procedure ISkControlRender.Resized = DeleteBuffers; + function TryRender(const ABackgroundBuffer: TBitmap; const AOpacity: Byte): Boolean; + end; + +procedure TSkRasterControlRender.DeleteBuffers; +begin + FDrawCached := False; + FreeAndNil(FIntfImg); +end; + +procedure TSkRasterControlRender.DoRender(const AWidth, AHeight: Integer; + const AScaleFactor: Single; const ACanvas: TCanvas); + + procedure InternalDraw; + var + LSurface: ISkSurface; + LDestRect: TRectF; + begin + LSurface := TSkSurface.MakeRasterDirect(TSkImageInfo.Create(AWidth, AHeight), + FIntfImg.PixelData, FIntfImg.DataDescription.BytesPerLine); + if LSurface = nil then + Exit; + LSurface.Canvas.Concat(TMatrix.CreateScaling(AScaleFactor, AScaleFactor)); + LDestRect := RectF(0, 0, AWidth / AScaleFactor, AHeight / AScaleFactor); + LSurface.Canvas.Clear(TAlphaColors.Null); + FTarget.Draw(LSurface.Canvas, LDestRect, 1); + FDrawCached := True; + end; + +var + Desc: TRawImageDescription; + Bmp: TBitmap; +begin + if (AWidth <= 0) or (AHeight <= 0) then + Exit; + + if FIntfImg=nil then + begin + if SkNative32ColorType=TSkColorType.BGRA8888 then + Desc.Init_BPP32_B8G8R8A8_BIO_TTB(AWidth, AHeight) + else + Desc.Init_BPP32_R8G8B8A8_BIO_TTB(AWidth, AHeight); + FIntfImg:=TLazIntfImage.Create(0,0); + FIntfImg.DataDescription:=Desc; + FIntfImg.SetSize(AWidth,AHeight); + end else if (FIntfImg.Width<>AWidth) or (FIntfImg.Height<>AHeight) then begin + FIntfImg.SetSize(AWidth,AHeight); + end; + + if (not FDrawCached) or (FTarget.DrawCacheKind = TSkDrawCacheKind.Never) then + InternalDraw; + + Bmp:=TBitmap.Create; + try + Bmp.LoadFromIntfImage(FIntfImg); + ACanvas.Draw(0,0,Bmp); + finally + Bmp.Free; + end; +end; + +constructor TSkRasterControlRender.Create( + const ATarget: ISkControlRenderTarget); +begin + inherited Create; + FTarget := ATarget; +end; + +destructor TSkRasterControlRender.Destroy; +begin + DeleteBuffers; + inherited; +end; + +procedure TSkRasterControlRender.Redraw; +begin + FDrawCached := False; +end; + +procedure TSkRasterControlRender.Resized; +begin + +end; + +function TSkRasterControlRender.TryRender(const ABackgroundBuffer: TBitmap; const AOpacity: Byte): Boolean; +begin + if AOpacity=0 then exit; + if ABackgroundBuffer=nil then ; + DoRender(FTarget.ClientWidth, FTarget.ClientHeight, FTarget.ScaleFactor, FTarget.Canvas{, ABackgroundBuffer, AOpacity}); + Result := True; +end; + +{ TSkControlRender } + +class function TSkControlRender.MakeRender( + const ATarget: ISkControlRenderTarget; const ABackend: TSkControlRenderBackend + ): ISkControlRender; +begin + if ATarget=nil then ; + case ABackend of + TSkControlRenderBackend.Default, + TSkControlRenderBackend.Raster: Result := TSkRasterControlRender.Create(ATarget); + TSkControlRenderBackend.HardwareAcceleration: Result := nil; //TSkGlControlRender.Create(ATarget); + else + Result := nil{%H-}; + end; +end; + +{ TSkCustomWinControl } + +function TSkCustomWinControl.GetRender: ISkControlRender; +begin + if FRender = nil then + FRender := MakeRender(FBackendRender); + Result := FRender; +end; + +procedure TSkCustomWinControl.DeleteBuffers; +begin + +end; + +procedure TSkCustomWinControl.SetBackendRender(AValue: TSkControlRenderBackend); +begin + if FBackendRender=AValue then Exit; + FBackendRender := AValue; + FRender := nil; +end; + +procedure TSkCustomWinControl.SetBackgroundColor(AValue: TAlphaColor); +begin + if FBackgroundColor=AValue then Exit; + FBackgroundColor:=AValue; + Invalidate; +end; + +procedure TSkCustomWinControl.SetDrawCacheKind(AValue: TSkDrawCacheKind); +begin + if FDrawCacheKind=AValue then Exit; + FDrawCacheKind:=AValue; + if FDrawCacheKind <> TSkDrawCacheKind.Always then + Invalidate; +end; + +procedure TSkCustomWinControl.SetOnDraw(AValue: TSkDrawEvent); +begin + if FOnDraw=AValue then Exit; + FOnDraw:=AValue; + Invalidate; +end; + +procedure TSkCustomWinControl.DestroyWnd; +begin + FRender:=nil; + inherited DestroyWnd; +end; + +procedure TSkCustomWinControl.DrawContent(const ACanvas: ISkCanvas; + const ADest: TRectF; const AOpacity: Single); +begin + if csDesigning in ComponentState then + DrawDesignBorder(ACanvas, ADest, AOpacity); + if Assigned(FOnDraw) then + FOnDraw(Self, ACanvas, ADest, AOpacity); +end; + +procedure TSkCustomWinControl.Draw(const ACanvas: ISkCanvas; + const ADest: TRectF; const AOpacity: Single); +var + LPaint: ISkPaint; +begin + if TAlphaColorRec(FBackgroundColor).A > 0 then + begin + LPaint := TSkPaint.Create; + LPaint.Color := FBackgroundColor; + LPaint.AntiAlias := True; + ACanvas.DrawRect(ADest, LPaint); + end; + DrawContent(ACanvas,ADest,AOpacity); +end; + +function TSkCustomWinControl.MakeRender(ABackendRender: TSkControlRenderBackend + ): ISkControlRender; +begin + Result := TSkControlRender.MakeRender(Self, ABackendRender); +end; + +procedure TSkCustomWinControl.Paint; +var + LBackgroundBuffer: TBitmap; +begin + if (Width <= 0) or (Height <= 0) or (Render = nil) then + Exit; + LBackgroundBuffer := nil; + if not FRender.TryRender(LBackgroundBuffer, 255) + and (FBackendRender = TSkControlRenderBackend.HardwareAcceleration) then + begin + FRender := MakeRender(TSkControlRenderBackend.Raster); + if FRender <> nil then + FRender.Redraw; + end; + inherited Paint; +end; + +procedure TSkCustomWinControl.Resize; +begin + if FRender <> nil then + FRender.Resized; + DeleteBuffers; + inherited Resize; +end; + +class function TSkCustomWinControl.GetControlClassDefaultSize: TSize; +begin + Result.cx:=80; + Result.cy:=80; +end; + +function TSkCustomWinControl.GetCanvas: TCanvas; +begin + Result := Canvas; +end; + +function TSkCustomWinControl.GetDrawCacheKind: TSkDrawCacheKind; +begin + Result := FDrawCacheKind; +end; + +function TSkCustomWinControl.GetClientHeight: Integer; +begin + Result := ClientHeight; +end; + +function TSkCustomWinControl.GetScaleFactor: Single; +begin + Result := ScaleFactor; +end; + +function TSkCustomWinControl.GetClientWidth: Integer; +begin + Result := ClientWidth; +end; + +procedure TSkCustomWinControl.ChangeScale(Multiplier, Divider: Integer); +begin + if Multiplier <> Divider then + FScaleFactor := FScaleFactor * Multiplier / Divider; + inherited ChangeScale(Multiplier, Divider); +end; + +constructor TSkCustomWinControl.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + FBackgroundColor := TAlphaColors.White; + FDrawCacheKind := TSkDrawCacheKind.Raster; + FScaleFactor := 1; + ControlStyle:=ControlStyle+[csAcceptsControls,csOpaque]; +end; + +destructor TSkCustomWinControl.Destroy; +begin + DeleteBuffers; + FRender := nil; + inherited Destroy; +end; + +{ TSkDefaultProviders } + +class constructor TSkDefaultProviders.Create; +begin + FTypefaceFont := TSkTypefaceFontProvider.Create; +end; + +class procedure TSkDefaultProviders.RegisterTypeface(const AFileName: string); +begin + FTypefaceFont.RegisterTypeface(TSkTypeFace.MakeFromFile(UnicodeString(AFileName))); +end; + +class procedure TSkDefaultProviders.RegisterTypeface(const AStream: TStream); +begin + FTypefaceFont.RegisterTypeface(TSkTypeFace.MakeFromStream(AStream)); +end; + +end. + diff --git a/components/skia/src/LCL.SkiaInit.pas b/components/skia/src/LCL.SkiaInit.pas new file mode 100644 index 000000000..673f1b944 --- /dev/null +++ b/components/skia/src/LCL.SkiaInit.pas @@ -0,0 +1,28 @@ +{ + The System.Skia.API unit loads the libsk4d library in its class constructors, + which eats any error message. + This unit logs an error via the LazLogger unit. + +} +unit LCL.SkiaInit; + +{$mode ObjFPC}{$H+} + +interface + +uses + SysUtils, LazLogger, System.Skia.API; + +implementation + +initialization + try + SkInitialize; + except + on E: Exception do begin + DebugLn('Failed loading skia libb: '+E.Message); + Halt(1); + end; + end; +end. + diff --git a/components/skia/src/SkiaFPC.pas b/components/skia/src/SkiaFPC.pas new file mode 100644 index 000000000..002152003 --- /dev/null +++ b/components/skia/src/SkiaFPC.pas @@ -0,0 +1,57 @@ +unit SkiaFPC; + +{$mode ObjFPC}{$H+} +{$ModeSwitch advancedrecords} + +interface + +uses + Classes, SysUtils, Types; + +type + TEpsilon = record + const + Matrix = 1E-5; + Vector = 1E-4; + Scale = 1E-4; + FontSize = 1E-2; + Position = 1E-3; + Angle = 1E-4; + end; + +// System.Types + +// move center of R to center of Bounds and return R +function RectCenter(var R: TRect; const Bounds: TRect): TRect; +function RectCenter(var R: TRectF; const Bounds: TRectF): TRectF; + +implementation + +function RectCenter(var R: TRect; const Bounds: TRect): TRect; +var + d: integer; +begin + d:=(Bounds.Left+Bounds.Right - R.Left-R.Right) div 2; + inc(R.Left,d); + inc(R.Right,d); + d:=(Bounds.Top+Bounds.Bottom - R.Top-R.Bottom) div 2; + inc(R.Top,d); + inc(R.Bottom,d); + Result:=R; +end; + +function RectCenter(var R: TRectF; const Bounds: TRectF): TRectF; +var + d: single; +begin + d:=(Bounds.Left+Bounds.Right - R.Left-R.Right)/2; + R.Left+=d; + R.Right+=d; + d:=(Bounds.Top+Bounds.Bottom - R.Top-R.Bottom)/2; + R.Top+=d; + R.Bottom+=d; + Result:=R; +end; + +end. + diff --git a/components/skia/src/skia.lcl.lpk b/components/skia/src/skia.lcl.lpk new file mode 100644 index 000000000..95dedb5e6 --- /dev/null +++ b/components/skia/src/skia.lcl.lpk @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <Package Version="5"> + <Name Value="Skia.LCL"/> + <Type Value="RunAndDesignTime"/> + <Author Value="Mattias Gaertner"/> + <CompilerOptions> + <Version Value="11"/> + <SearchPaths> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + </CompilerOptions> + <Description Value="LCL components with a skia canvas using skia4delphi, requires FPC 3.3.1+"/> + <Version Major="1"/> + <Files> + <Item> + <Filename Value="LCL.Skia.pas"/> + <UnitName Value="LCL.Skia"/> + </Item> + <Item> + <Filename Value="LCL.SkiaInit.pas"/> + <UnitName Value="LCL.SkiaInit"/> + </Item> + <Item> + <Filename Value="SkiaFPC.pas"/> + <UnitName Value="SkiaFPC"/> + </Item> + </Files> + <RequiredPkgs> + <Item> + <PackageName Value="Skia"/> + </Item> + <Item> + <PackageName Value="LCL"/> + </Item> + </RequiredPkgs> + <UsageOptions> + <UnitPath Value="$(PkgOutDir)"/> + </UsageOptions> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + </Package> +</CONFIG> diff --git a/components/skia/src/skia.lcl.pas b/components/skia/src/skia.lcl.pas new file mode 100644 index 000000000..90c3af0d8 --- /dev/null +++ b/components/skia/src/skia.lcl.pas @@ -0,0 +1,21 @@ +{ This file was automatically created by Lazarus. Do not edit! + This source is only used to compile and install the package. + } + +unit Skia.LCL; + +{$warn 5023 off : no warning about unused units} +interface + +uses + LCL.Skia, LCL.SkiaInit, SkiaFPC, LazarusPackageIntf; + +implementation + +procedure Register; +begin +end; + +initialization + RegisterPackage('Skia.LCL', @Register); +end.