From 00e6982f5b6772c77e57e28d0cb0495033dbbafc Mon Sep 17 00:00:00 2001 From: wqmeng Date: Sat, 23 Aug 2025 20:16:28 +0800 Subject: [PATCH 01/11] Support Flip, CenterAllPaths by their bounds, Add GetPathBounds. KeepAspectRatio now is configurable default is true. --- Image32/source/Img32.SVG.Reader.pas | 60 +++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/Image32/source/Img32.SVG.Reader.pas b/Image32/source/Img32.SVG.Reader.pas index 55d90f8..e28aa18 100644 --- a/Image32/source/Img32.SVG.Reader.pas +++ b/Image32/source/Img32.SVG.Reader.pas @@ -165,6 +165,9 @@ TSvgReader = class fRootElement : TSvgElement; fFontCache : TFontCache; fKeepAspectRatio : Boolean; + fFlipHorizontal : Boolean; + fFlipVertical : Boolean; + function LoadInternal: Boolean; function GetIsEmpty: Boolean; function GetTempImage: TImage32; @@ -188,6 +191,9 @@ TSvgReader = class function LoadFromString(const str: string): Boolean; function GetImageSize: TSize; + function GetPathBounds: TRectD; + function DrawFullPathsInCenter: Boolean; + // The following two methods are deprecated and intended only for ... // https://github.com/EtheaDev/SVGIconImageList procedure SetOverrideFillColor(color: TColor32); //deprecated; @@ -199,9 +205,10 @@ TSvgReader = class property IsEmpty : Boolean read GetIsEmpty; // KeepAspectRatio: this property has also been added for the convenience of // the third-party SVGIconImageList. (IMHO it should always = true) - property KeepAspectRatio: Boolean - read fKeepAspectRatio write fKeepAspectRatio; + property KeepAspectRatio : Boolean read fKeepAspectRatio write fKeepAspectRatio; property RootElement : TSvgElement read fRootElement; + property FlipHorizontal : Boolean read fFlipHorizontal write fFlipHorizontal; + property FlipVertical : Boolean read fFlipVertical write fFlipVertical; end; var @@ -5644,6 +5651,23 @@ procedure TSvgReader.ResetPaths; end; //------------------------------------------------------------------------------ +function TSvgReader.DrawFullPathsInCenter: Boolean; +var + LRectD: TRectD; +begin + Result := false; + LRectD:= GetPathBounds; + if LRectD.IsEmpty then + Exit; + + Self.RootElement.viewboxWH.Left := LRectD.Left; + Self.RootElement.viewboxWH.Top := LRectD.Top; + Self.RootElement.viewboxWH.Width := LRectD.Width; + Self.RootElement.viewboxWH.Height := LRectD.Height; + Result := true; +end; +//------------------------------------------------------------------------------ + procedure TSvgReader.DrawImage(img: TImage32; scaleToImage: Boolean); var scale, scaleH: double; @@ -5666,8 +5690,29 @@ procedure TSvgReader.DrawImage(img: TImage32; scaleToImage: Boolean); if di.currentColor = clInvalid then di.currentColor := currentColor; +// MatrixIdentity(di.matrix); + MatrixTranslate(di.matrix, -viewboxWH.Left, -viewboxWH.Top); + // flips done in *viewbox space* (unit = SVG units), not with -scale + if fFlipHorizontal and fFlipVertical then + begin + MatrixScale(di.matrix, -1, -1); + MatrixTranslate(di.matrix, viewboxWH.Width, viewboxWH.Height); + end else + if fFlipHorizontal then + begin + // mirror around vertical axis of viewbox + MatrixScale(di.matrix, -1, 1); + MatrixTranslate(di.matrix, viewboxWH.Width, 0); + end else + if fFlipVertical then + begin + // mirror around horizontal axis of viewbox + MatrixScale(di.matrix, 1, -1); + MatrixTranslate(di.matrix, 0, viewboxWH.Height); + end; + //the width and height attributes generally indicate the size of the //rendered image unless they are percentage values. Nevertheless, these //values can be still overridden by the scaleToImage parameter above @@ -5697,7 +5742,6 @@ procedure TSvgReader.DrawImage(img: TImage32; scaleToImage: Boolean); Round(viewboxWH.Height * scaleH)); end else img.SetSize(Round(viewboxWH.Width), Round(viewboxWH.Height)); - end; if fBkgndColor <> clNone32 then @@ -5843,6 +5887,16 @@ function TSvgReader.GetIsEmpty: Boolean; begin Result := not Assigned(fRootElement); end; + +function TSvgReader.GetPathBounds: TRectD; +begin + if not Assigned(Self.fRootElement) then begin + Result := NullRectD; + Exit; + end; + Result := Self.fRootElement.GetBounds; +end; + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ From 9be196474182f4a53471fd107515e7e0e42ba8df Mon Sep 17 00:00:00 2001 From: wqmeng Date: Sat, 23 Aug 2025 20:24:55 +0800 Subject: [PATCH 02/11] Add Flip, GetPathBounds, DrawFullPathsInCenter functions and properties. --- Source/SVGInterfaces.pas | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Source/SVGInterfaces.pas b/Source/SVGInterfaces.pas index 06b4d55..6cbc64c 100644 --- a/Source/SVGInterfaces.pas +++ b/Source/SVGInterfaces.pas @@ -14,7 +14,8 @@ interface System.Types, System.UITypes, System.SysUtils, - System.Classes; + System.Classes, + Img32; const SVG_INHERIT_COLOR = TColors.SysDefault; @@ -37,6 +38,12 @@ ESVGException = class(Exception); procedure SetFixedColor(const Color: TColor); function GetApplyFixedColorToRootOnly: Boolean; procedure SetApplyFixedColorToRootOnly(Value:Boolean); + function GetApplyDrawFullPathsInCenter: Boolean; + procedure SetApplyDrawFullPathsInCenter(Value:Boolean); + function GetFlipHorizontal: Boolean; + procedure SetFlipHorizontal(Value:Boolean); + function GetFlipVertically: Boolean; + procedure SetFlipVertically(Value:Boolean); function GetSource: string; procedure SetSource(const ASource: string); // procedures and functions @@ -47,6 +54,9 @@ ESVGException = class(Exception); procedure LoadFromStream(Stream: TStream); procedure LoadFromFile(const FileName: string); procedure PaintTo(DC: HDC; R: TRectF; KeepAspectRatio: Boolean = True); + function GetPathBounds: TRectD; + function DrawFullPathsInCenter: Boolean; + // properties property Width: Single read GetWidth; property Height: Single read GetHeight; @@ -55,6 +65,10 @@ ESVGException = class(Exception); property FixedColor: TColor read GetFixedColor write SetFixedColor; property ApplyFixedColorToRootOnly: Boolean read GetApplyFixedColorToRootOnly write SetApplyFixedColorToRootOnly; + property FlipHorizontal: Boolean read GetFlipHorizontal write SetFlipHorizontal; + property FlipVertically: Boolean read GetFlipVertically write SetFlipVertically; + property ApplyDrawFullPathsInCenter: Boolean read GetApplyDrawFullPathsInCenter + write SetApplyDrawFullPathsInCenter; property Source: string read GetSource write SetSource; end; From b260f36bb356b3573cc9eccecb51b5a22e30dc03 Mon Sep 17 00:00:00 2001 From: wqmeng Date: Sat, 23 Aug 2025 20:28:02 +0800 Subject: [PATCH 03/11] Add Flip, CenterPaths for Image32 VCL. --- Source/Image32SVGFactory.pas | 90 +++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/Source/Image32SVGFactory.pas b/Source/Image32SVGFactory.pas index 8863828..255ae10 100644 --- a/Source/Image32SVGFactory.pas +++ b/Source/Image32SVGFactory.pas @@ -32,7 +32,8 @@ implementation Img32.SVG.Core, //because is the best engine available with SVGIconImageList. Img32.SVG.Reader, //If you don't want to use it change SVGIconImageList.inc Img32.Text, //Otherwise you must add this search path: - Img32.Vector; //- SVGIconImageList\Image32\Source + Img32.Vector, //- SVGIconImageList\Image32\Source + Img32.Transform; type TImage32SVG = class(TInterfacedObject, ISVG) @@ -43,6 +44,7 @@ TImage32SVG = class(TInterfacedObject, ISVG) FHeight: Single; FFixedColor: TColor; FApplyFixedColorToRootOnly: Boolean; + FApplyDrawFullPathsInCenter: Boolean; FGrayScale: Boolean; FOpacity: Single; FImage32: TImage32; @@ -73,9 +75,18 @@ TImage32SVG = class(TInterfacedObject, ISVG) {$IFDEF CheckForUnsupportedSvg} procedure CheckForUnsupportedSvg; {$ENDIF} + function GetApplyDrawFullPathsInCenter: Boolean; + procedure SetApplyDrawFullPathsInCenter(Value:Boolean); + function GetFlipHorizontal: Boolean; + procedure SetFlipHorizontal(Value:Boolean); + function GetFlipVertically: Boolean; + procedure SetFlipVertically(Value:Boolean); public constructor Create; destructor Destroy; override; + + function GetPathBounds: TRectD; + function DrawFullPathsInCenter: Boolean; end; TImage32SVGFactory = class(TInterfacedObject, ISVGFactory) @@ -107,6 +118,20 @@ destructor TImage32SVG.Destroy; inherited; end; +function TImage32SVG.DrawFullPathsInCenter; +var + LRect: TRectD; +begin + Result := false; + LRect:= GetPathBounds; + if LRect.IsEmpty then + Exit; + + if Assigned(fSvgReader) then begin + Result := Self.fSvgReader.DrawFullPathsInCenter; + end; +end; + procedure TImage32SVG.LoadFromFile(const FileName: string); Var FileStream: TFileStream; @@ -119,6 +144,11 @@ procedure TImage32SVG.LoadFromFile(const FileName: string); end; end; +function TImage32SVG.GetApplyDrawFullPathsInCenter: Boolean; +begin + Result := FApplyDrawFullPathsInCenter; +end; + function TImage32SVG.GetApplyFixedColorToRootOnly: Boolean; begin Result := FApplyFixedColorToRootOnly; @@ -129,6 +159,24 @@ function TImage32SVG.GetFixedColor: TColor; Result := FFixedColor; end; +function TImage32SVG.GetFlipHorizontal: Boolean; +begin + if Assigned(fSvgReader) then begin + Result := fSvgReader.FlipHorizontal; + Exit; + end; + Result := false; +end; + +function TImage32SVG.GetFlipVertically: Boolean; +begin + if Assigned(fSvgReader) then begin + Result := fSvgReader.FlipVertical; + Exit; + end; + Result := false; +end; + function TImage32SVG.GetGrayScale: Boolean; begin Result := FGrayScale; @@ -144,6 +192,14 @@ function TImage32SVG.GetOpacity: Single; Result := FOpacity; end; +function TImage32SVG.GetPathBounds: TRectD; +begin + if Self.fSvgReader <> nil then + Result := Self.fSvgReader.GetPathBounds + else + Result := NullRectD; +end; + function TImage32SVG.GetSource: string; begin Result := FSource; @@ -222,6 +278,10 @@ procedure TImage32SVG.PaintTo(DC: HDC; R: TRectF; KeepAspectRatio: Boolean); //Draw SVG image to FImage32 FsvgReader.DrawImage(FImage32, True); + if Self.FApplyDrawFullPathsInCenter then begin + if Self.DrawFullPathsInCenter then + FsvgReader.DrawImage(FImage32, True); + end; //assuming KeepAspectRatio = true, prepare to center dx := (R.Width - FImage32.Width) *0.5; @@ -261,6 +321,20 @@ procedure TImage32SVG.SaveToStream(Stream: TStream); Stream.WriteBuffer(Buffer, Length(Buffer)) end; +procedure TImage32SVG.SetApplyDrawFullPathsInCenter(Value: Boolean); +var + LSource: String; +begin + if FApplyDrawFullPathsInCenter <> Value then begin + FApplyDrawFullPathsInCenter := Value; + if FApplyDrawFullPathsInCenter then + Self.DrawFullPathsInCenter + else begin + LoadFromSource; + end; + end; +end; + procedure TImage32SVG.SetApplyFixedColorToRootOnly(Value: Boolean); var Color: TColor; @@ -292,6 +366,20 @@ procedure TImage32SVG.SetFixedColor(const Color: TColor); FGrayScale := False; end; +procedure TImage32SVG.SetFlipHorizontal(Value: Boolean); +begin + if Assigned(fSvgReader) then begin + fSvgReader.FlipHorizontal := Value; + end; +end; + +procedure TImage32SVG.SetFlipVertically(Value: Boolean); +begin + if Assigned(fSvgReader) then begin + fSvgReader.FlipVertical := Value; + end; +end; + procedure TImage32SVG.SetGrayScale(const IsGrayScale: Boolean); begin if IsGrayScale = FGrayScale then Exit; From ea63384bc709fcbc0d680e9b389996d9d1b0ec96 Mon Sep 17 00:00:00 2001 From: wqmeng Date: Sat, 23 Aug 2025 20:33:43 +0800 Subject: [PATCH 04/11] Implement ISVG flip and centerAllPaths functions. Get Flip to work. CenterAllPaths feature not work on Skia currently. --- Source/SkiaSVGFactory.pas | 98 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/Source/SkiaSVGFactory.pas b/Source/SkiaSVGFactory.pas index a7b0e34..85e518b 100644 --- a/Source/SkiaSVGFactory.pas +++ b/Source/SkiaSVGFactory.pas @@ -31,7 +31,8 @@ implementation System.Math.Vectors, //Skia engine Vcl.Skia, - System.Skia; + System.Skia, + Img32; type { TSkSvgBrushEx } @@ -61,8 +62,10 @@ TSkiaSVG = class(TInterfacedObject, ISVG) FHeight: Integer; FFixedColor: TColor; FApplyFixedColorToRootOnly: Boolean; + FApplyDrawFullPathsInCenter: Boolean; FGrayScale: Boolean; FOpacity: Single; + FFlipHorizontal, FFlipVertically: Boolean; // property access methods function GetWidth: Single; function GetHeight: Single; @@ -89,9 +92,17 @@ TSkiaSVG = class(TInterfacedObject, ISVG) procedure CreateBuffer(const AWidth, AHeight: Integer; const AMemDC: HDC; out ABuffer: HBITMAP; out AData: Pointer; out AStride: Integer); + function GetApplyDrawFullPathsInCenter: Boolean; + procedure SetApplyDrawFullPathsInCenter(Value:Boolean); + function GetFlipHorizontal: Boolean; + procedure SetFlipHorizontal(Value:Boolean); + function GetFlipVertically: Boolean; + procedure SetFlipVertically(Value:Boolean); public constructor Create; destructor Destroy; override; + function GetPathBounds: TRectD; + function DrawFullPathsInCenter: Boolean; end; TSkiaSVGFactory = class(TInterfacedObject, ISVGFactory) @@ -162,6 +173,11 @@ destructor TSkiaSVG.Destroy; inherited; end; +function TSkiaSVG.DrawFullPathsInCenter: Boolean; +begin + Result := false; // Not implementation. +end; + procedure TSkiaSVG.CreateBuffer( const AWidth, AHeight: Integer; const AMemDC: HDC; out ABuffer: HBITMAP; @@ -220,6 +236,11 @@ procedure TSkiaSVG.LoadFromFile(const FileName: string); end; end; +function TSkiaSVG.GetApplyDrawFullPathsInCenter: Boolean; +begin + Result := FApplyDrawFullPathsInCenter; +end; + function TSkiaSVG.GetApplyFixedColorToRootOnly: Boolean; begin Result := FApplyFixedColorToRootOnly; @@ -230,6 +251,16 @@ function TSkiaSVG.GetFixedColor: TColor; Result := FFixedColor; end; +function TSkiaSVG.GetFlipHorizontal: Boolean; +begin + Result := FFlipHorizontal; +end; + +function TSkiaSVG.GetFlipVertically: Boolean; +begin + Result := FFlipVertically; +end; + function TSkiaSVG.GetGrayScale: Boolean; begin Result := FGrayScale; @@ -245,6 +276,14 @@ function TSkiaSVG.GetOpacity: Single; Result := FOpacity; end; +function TSkiaSVG.GetPathBounds: TRectD; +begin + Result.Left := 0; + Result.Top := 0; + Result.Right := 0; + Result.Bottom := 0; +end; + function TSkiaSVG.GetSource: string; begin Result := FSvg.Source; @@ -268,11 +307,11 @@ procedure TSkiaSVG.LoadFromSource; procedure TSkiaSVG.PaintTo(DC: HDC; R: TRectF; KeepAspectRatio: Boolean); const - BlendFunction: TBlendFunction = (BlendOp: AC_SRC_OVER; BlendFlags: 0; SourceConstantAlpha: 255; AlphaFormat: AC_SRC_ALPHA); + BlendFunction: Winapi.Windows.TBlendFunction = (BlendOp: AC_SRC_OVER; BlendFlags: 0; SourceConstantAlpha: 255; AlphaFormat: AC_SRC_ALPHA); var LOldObj: HGDIOBJ; LDrawBufferDC: HDC; - LBlendFunction: TBlendFunction; + LBlendFunction: Winapi.Windows.TBlendFunction; LScaleFactor: Single; function ColorToAlphaColor(Value: TColor): TAlphaColor; @@ -292,9 +331,34 @@ procedure TSkiaSVG.PaintTo(DC: HDC; R: TRectF; KeepAspectRatio: Boolean); var LSurface: ISkSurface; LDestRect: TRectF; + FlipMatrix: TMatrix; begin LSurface := TSkSurface.MakeRasterDirect(TSkImageInfo.Create(FWidth, FHeight), FDrawBufferData, FDrawBufferStride); LSurface.Canvas.Clear(TAlphaColors.Null); + + // Prepare scaling for flip + FlipMatrix := TMatrix.Identity; + + if FFlipHorizontal then + begin + // flip around vertical axis through the center + FlipMatrix := FlipMatrix * + TMatrix.CreateTranslation(-FWidth/2, 0) * + TMatrix.CreateScaling(-1, 1) * + TMatrix.CreateTranslation(FWidth/2, 0); + end; + + if FFlipVertically then + begin + // flip around horizontal axis through the center + FlipMatrix := FlipMatrix * + TMatrix.CreateTranslation(0, -FHeight/2) * + TMatrix.CreateScaling(1, -1) * + TMatrix.CreateTranslation(0, FHeight/2); + end; + + LSurface.Canvas.Concat(FlipMatrix); + LScaleFactor := 1; LSurface.Canvas.Concat(TMatrix.CreateScaling(LScaleFactor, LScaleFactor)); LDestRect := RectF(0, 0, FWidth / LScaleFactor, FHeight / LScaleFactor); @@ -383,6 +447,18 @@ procedure TSkiaSVG.SaveToStream(Stream: TStream); Stream.WriteBuffer(Buffer, Length(Buffer)) end; +procedure TSkiaSVG.SetApplyDrawFullPathsInCenter(Value: Boolean); +begin + if FApplyDrawFullPathsInCenter <> Value then begin + FApplyDrawFullPathsInCenter := Value; + if FApplyDrawFullPathsInCenter then + Self.DrawFullPathsInCenter + else begin + LoadFromSource; + end; + end; +end; + procedure TSkiaSVG.SetApplyFixedColorToRootOnly(Value: Boolean); var Color: TColor; @@ -414,6 +490,22 @@ procedure TSkiaSVG.SetFixedColor(const Color: TColor); FGrayScale := False; end; +procedure TSkiaSVG.SetFlipHorizontal(Value: Boolean); +begin + if FFlipHorizontal <> Value then begin + FFlipHorizontal := Value; + LoadFromSource; + end; +end; + +procedure TSkiaSVG.SetFlipVertically(Value: Boolean); +begin + if FFlipVertically <> Value then begin + FFlipVertically := Value; + LoadFromSource; + end; +end; + procedure TSkiaSVG.SetGrayScale(const IsGrayScale: Boolean); begin if IsGrayScale = FGrayScale then Exit; From e31b405d203abe5daa17b5d6e3fa0379052a78cb Mon Sep 17 00:00:00 2001 From: wqmeng Date: Sat, 23 Aug 2025 20:34:54 +0800 Subject: [PATCH 05/11] Implement ISVG flip and centerAllPaths functions. Get Flip to work. CenterAllPaths feature not work on D2DNative currently. --- Source/D2DSVGFactory.pas | 96 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/Source/D2DSVGFactory.pas b/Source/D2DSVGFactory.pas index 8dbf9fe..356a5ba 100644 --- a/Source/D2DSVGFactory.pas +++ b/Source/D2DSVGFactory.pas @@ -49,7 +49,8 @@ implementation System.UIConsts, System.SysUtils, System.Classes, - System.RegularExpressions; + System.RegularExpressions, + Img32; resourcestring D2D_ERROR_NOT_AVAILABLE = 'Windows SVG support is not available'; @@ -64,9 +65,11 @@ TD2DSVG = class(TInterfacedObject, ISVG) fHeight: Single; fFixedColor: TColor; fApplyFixedColorToRootOnly: Boolean; + fApplyDrawFullPathsInCenter: Boolean; fGrayScale: Boolean; fOpacity: Single; fSvgDoc: ID2D1SvgDocument; + fFlipHorizontal, fFlipVertically: Boolean; // property access methods function GetWidth: Single; function GetHeight: Single; @@ -94,8 +97,16 @@ TD2DSVG = class(TInterfacedObject, ISVG) {$IFDEF CheckForUnsupportedSvg} procedure CheckForUnsupportedSvg; {$ENDIF} + function GetApplyDrawFullPathsInCenter: Boolean; + procedure SetApplyDrawFullPathsInCenter(Value:Boolean); + function GetFlipHorizontal: Boolean; + procedure SetFlipHorizontal(Value:Boolean); + function GetFlipVertically: Boolean; + procedure SetFlipVertically(Value:Boolean); public constructor Create; + function GetPathBounds: TRectD; + function DrawFullPathsInCenter: Boolean; end; TD2DSVGHandler = class(TInterfacedObject, ISVGFactory) function NewSvg: ISVG; @@ -244,6 +255,11 @@ constructor TD2DSVG.Create; FOpacity := 1.0; end; +function TD2DSVG.DrawFullPathsInCenter: Boolean; +begin + Result := false; // Not implementation. +end; + procedure TD2DSVG.SvgFromStream(Stream: TStream); var XStream: IStream; @@ -321,6 +337,11 @@ procedure TD2DSVG.LoadFromSource; end; end; +function TD2DSVG.GetApplyDrawFullPathsInCenter: Boolean; +begin + Result := FApplyDrawFullPathsInCenter; +end; + function TD2DSVG.GetApplyFixedColorToRootOnly: Boolean; begin Result := fApplyFixedColorToRootOnly; @@ -331,6 +352,16 @@ function TD2DSVG.GetFixedColor: TColor; Result := fFixedColor; end; +function TD2DSVG.GetFlipHorizontal: Boolean; +begin + Result := FFlipHorizontal; +end; + +function TD2DSVG.GetFlipVertically: Boolean; +begin + Result := FFlipVertically; +end; + function TD2DSVG.GetGrayScale: Boolean; begin Result := fGrayScale; @@ -355,6 +386,14 @@ function TD2DSVG.GetOpacity: Single; end; end; +function TD2DSVG.GetPathBounds: TRectD; +begin + Result.Left := 0; + Result.Top := 0; + Result.Right := 0; + Result.Bottom := 0; +end; + function TD2DSVG.GetSource: string; begin Result := FSource; @@ -389,12 +428,13 @@ procedure TD2DSVG.LoadFromStream(Stream: TStream); procedure TD2DSVG.PaintTo(DC: HDC; R: TRectF; KeepAspectRatio: Boolean); var - Matrix : TD2DMatrix3X2F; + Matrix, FlipMatrix : TD2DMatrix3X2F; SvgRect : TRectF; RT: ID2D1DCRenderTarget; Ratio: Single; Root: ID2D1SvgElement; NewColor: TD2D1ColorF; + Center: TD2DPoint2f; begin if not Assigned(fSvgDoc) then Exit; SvgRect:= R; @@ -411,6 +451,26 @@ procedure TD2DSVG.PaintTo(DC: HDC; R: TRectF; KeepAspectRatio: Boolean); R.Height/fHeight, Point(0, 0)); end; end; + + // Apply flip horizontal if requested + if fFlipHorizontal then + begin + // Flip horizontally around center of rect + Center.X := R.CenterPoint.X; + Center.Y := R.CenterPoint.Y; + FlipMatrix := TD2DMatrix3X2F.Scale(-1, 1, Center); + Matrix := Matrix * FlipMatrix; + end; + + if fFlipVertically then + begin + // Flip vertically around center of rect + Center.X := R.CenterPoint.X; + Center.Y := R.CenterPoint.Y; + FlipMatrix := TD2DMatrix3X2F.Scale(1, -1, Center); + Matrix := Matrix * FlipMatrix; + end; + //GrayScale if fGrayScale then begin @@ -471,6 +531,18 @@ procedure TD2DSVG.SaveToStream(Stream: TStream); Stream.WriteBuffer(Buffer, Length(Buffer)) end; +procedure TD2DSVG.SetApplyDrawFullPathsInCenter(Value: Boolean); +begin + if FApplyDrawFullPathsInCenter <> Value then begin + FApplyDrawFullPathsInCenter := Value; + if FApplyDrawFullPathsInCenter then + Self.DrawFullPathsInCenter + else begin + LoadFromSource; + end; + end; +end; + procedure TD2DSVG.SetApplyFixedColorToRootOnly(AValue: Boolean); var Color: TColor; @@ -500,6 +572,26 @@ procedure TD2DSVG.SetFixedColor(const Color: TColor); fGrayScale := False; end; +procedure TD2DSVG.SetFlipHorizontal(Value: Boolean); +var + LSource: String; +begin + if FFlipHorizontal <> Value then begin + FFlipHorizontal := Value; + LoadFromSource; + end; +end; + +procedure TD2DSVG.SetFlipVertically(Value: Boolean); +var + LSource: String; +begin + if FFlipVertically <> Value then begin + FFlipVertically := Value; + LoadFromSource; + end; +end; + procedure TD2DSVG.SetGrayScale(const IsGrayScale: Boolean); Var Root: ID2D1SvgElement; From add007fdd7b9657ac6ccefb160aa4da6d5bf2949 Mon Sep 17 00:00:00 2001 From: wqmeng Date: Sat, 23 Aug 2025 20:47:17 +0800 Subject: [PATCH 06/11] Get Flip, CenterAllPaths to work for FMX. --- Source/FMX.Image32SVG.pas | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/Source/FMX.Image32SVG.pas b/Source/FMX.Image32SVG.pas index 1ec249d..8184c94 100644 --- a/Source/FMX.Image32SVG.pas +++ b/Source/FMX.Image32SVG.pas @@ -57,6 +57,10 @@ TFmxImage32SVG = class(TFmxImageSVG) //Abstract methods procedure LoadFromSource; override; procedure LoadFromStream(Stream: TStream); override; + function GetFlipHorizontal: Boolean; override; + function GetFlipVertical: Boolean; override; + procedure SetFlipHorizontal(const Value: Boolean); override; + procedure SetFlipVertical(const Value: Boolean); override; public //Abstract methods function IsEmpty: Boolean; override; @@ -65,6 +69,9 @@ TFmxImage32SVG = class(TFmxImageSVG) constructor Create; override; destructor Destroy; override; + + function GetPathBounds:TRectF; override; + function DrawFullPathsInCenter: Boolean; override; end; implementation @@ -95,6 +102,47 @@ destructor TFmxImage32SVG.Destroy; inherited; end; +function TFmxImage32SVG.DrawFullPathsInCenter: Boolean; +var + LRect: TRect; +begin + Result := false; + + if Assigned(fSvgReader) then + Result := fSvgReader.DrawFullPathsInCenter; +end; + +function TFmxImage32SVG.GetFlipHorizontal: Boolean; +begin + if Assigned(fSvgReader) then begin + Result := fSvgReader.FlipHorizontal; + Exit; + end; + Result := false; +end; + +function TFmxImage32SVG.GetFlipVertical: Boolean; +begin + if Assigned(fSvgReader) then begin + Result := fSvgReader.FlipVertical; + Exit; + end; + Result := false; +end; + +function TFmxImage32SVG.GetPathBounds: TRectF; +var + LRectD: TRectD; +begin + if Assigned(fSvgReader) then begin + LRectD := fSvgReader.GetPathBounds; + + Result := TRectF.Create(LRectD.Left, LRectD.Top, LRectD.Right, LRectD.Bottom); + end + else + Result := TRectF.Empty; +end; + function TFmxImage32SVG.IsEmpty: Boolean; begin Result := fSvgReader.IsEmpty; @@ -158,6 +206,11 @@ procedure TFmxImage32SVG.PaintToBitmap(ABitmap: TBitmap; //Draw SVG image to FImage32 FsvgReader.DrawImage(FImage32, True); + if ApplyDrawFullPathsInCenter then begin + if DrawFullPathsInCenter then + FsvgReader.DrawImage(FImage32, True); + end; + //Apply GrayScale and FixedColor to Image32 if GrayScale then FImage32.Grayscale @@ -183,6 +236,18 @@ procedure TFmxImage32SVG.PaintToBitmap(ABitmap: TBitmap; end; end; +procedure TFmxImage32SVG.SetFlipHorizontal(const Value: Boolean); +begin + if Assigned(fSvgReader) then + fSvgReader.FlipHorizontal := Value; +end; + +procedure TFmxImage32SVG.SetFlipVertical(const Value: Boolean); +begin + if Assigned(fSvgReader) then + fSvgReader.FlipVertical := Value; +end; + initialization {$IFDEF MSWINDOWS} FontManager.LoadFontReaderFamily('Arial'); From 1acfb579bba89dc49eed510faf730641447acae3 Mon Sep 17 00:00:00 2001 From: wqmeng Date: Sat, 23 Aug 2025 20:49:11 +0800 Subject: [PATCH 07/11] Add virtual functions for base FMX ImageSVG to support Flip, and CenterAllPaths. Add virtual Assign function for fast copy a ImageSVG. --- Source/FMX.ImageSVG.pas | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Source/FMX.ImageSVG.pas b/Source/FMX.ImageSVG.pas index 77c68b4..f7e3945 100644 --- a/Source/FMX.ImageSVG.pas +++ b/Source/FMX.ImageSVG.pas @@ -39,7 +39,8 @@ interface , FMX.MultiResBitmap , FMX.Types , FMX.Graphics - , FMX.Objects; + , FMX.Objects + , img32; resourcestring SVG_ERROR_PARSING_SVG_TEXT = 'Error parsing SVG Text: %s'; @@ -53,6 +54,8 @@ TFmxImageSVG = class(TObject) FApplyFixedColorToRootOnly: Boolean; FGrayScale: Boolean; FOpacity: Single; + FKeepAspectRatio: Boolean; + FApplyDrawFullPathsInCenter: Boolean; // property access methods function GetOpacity: Single; procedure SetOpacity(const Opacity: Single); @@ -73,6 +76,10 @@ TFmxImageSVG = class(TObject) //Abstract methods procedure LoadFromSource; virtual; abstract; procedure LoadFromStream(Stream: TStream); virtual; abstract; + function GetFlipHorizontal: Boolean; virtual; abstract; + function GetFlipVertical: Boolean; virtual; abstract; + procedure SetFlipHorizontal(const Value: Boolean); virtual; abstract; + procedure SetFlipVertical(const Value: Boolean); virtual; abstract; public //Abstract methods function IsEmpty: Boolean; virtual; abstract; @@ -85,16 +92,32 @@ TFmxImageSVG = class(TObject) procedure SaveToFile(const FileName: string); procedure LoadFromFile(const FileName: string); procedure LoadFromText(const ASVGText: string); + + procedure Assign(Source: TPersistent); virtual; + function GetPathBounds: TRectF; virtual; abstract; + function DrawFullPathsInCenter: Boolean; virtual; abstract; + property Opacity: Single read GetOpacity write SetOpacity; property FixedColor: TAlphaColor read GetFixedColor write SetFixedColor; property GrayScale: Boolean read GetGrayScale write SetGrayScale; property Source: string read GetSource; property ApplyFixedColorToRootOnly: Boolean read GetApplyFixedColorToRootOnly write SetApplyFixedColorToRootOnly; + + property ApplyDrawFullPathsInCenter: Boolean read FApplyDrawFullPathsInCenter write FApplyDrawFullPathsInCenter; + property KeepAspectRatio: Boolean read FKeepAspectRatio write FKeepAspectRatio; + property FlipHorizontal: Boolean read GetFlipHorizontal write SetFlipHorizontal; + property FlipVertical: Boolean read GetFlipVertical write SetFlipVertical; end; implementation { TFmxImageSVG } +procedure TFmxImageSVG.Assign(Source: TPersistent); +begin + inherited; + +end; + procedure TFmxImageSVG.Clear; Const EmptySvg = ''; @@ -107,6 +130,7 @@ constructor TFmxImageSVG.Create; inherited; FFixedColor := TAlphaColorRec.Null; FOpacity := 1.0; + FKeepAspectRatio := True; end; destructor TFmxImageSVG.Destroy; From 6c672dedf6bd6f377c47ba129abee7396393f2df Mon Sep 17 00:00:00 2001 From: wqmeng Date: Sat, 23 Aug 2025 20:57:40 +0800 Subject: [PATCH 08/11] Support Flip, KeepAspectRatio, CenterAllPaths. Implement Assign function for fast copy from memory. --- Source/FMX.SVGIconImage.pas | 209 +++++++++++++++++++++++++++++++++++- 1 file changed, 208 insertions(+), 1 deletion(-) diff --git a/Source/FMX.SVGIconImage.pas b/Source/FMX.SVGIconImage.pas index fb6526b..185df9a 100644 --- a/Source/FMX.SVGIconImage.pas +++ b/Source/FMX.SVGIconImage.pas @@ -118,6 +118,14 @@ TSVGIconImage = class(TImage) function GetGrayScale: Boolean; procedure SetSVGText(AValue: string); function GetSVGText: string; + function GetKeepAspectRatio: Boolean; + procedure SetKeepAspectRatio(const Value: Boolean); + procedure SetFlipHorizontal(const Value: Boolean); + procedure SetFlipVertical(const Value: Boolean); + function GetApplyDrawFullPathsInCenter: Boolean; + procedure SetApplyDrawFullPathsInCenter(const Value: Boolean); + function GetFlipHorizontal: Boolean; + function GetFlipVertical: Boolean; protected function CreateMultiResBitmap: TFixedMultiResBitmap; override; public @@ -127,10 +135,21 @@ TSVGIconImage = class(TImage) procedure LoadFromFile(const AFileName: string); procedure SaveToFile(const AFileName: string); function GetFixedBitmap: TSVGIconFixedBitmapItem; + + function GetPathBounds: TRectF; + function DrawFullPathsInCenter: Boolean; + procedure Assign(Source: TPersistent); override; + published property BitmapZoom: Integer read FZoom write SetBitmapZoom default ZOOM_DEFAULT; property FixedColor: TAlphaColor read GetFixedColor write SetFixedColor default TAlphaColorRec.Null; property GrayScale: Boolean read GetGrayScale write SetGrayScale default False; + + property KeepAspectRatio: Boolean read GetKeepAspectRatio write SetKeepAspectRatio default True; + property FlipHorizontal: Boolean read GetFlipHorizontal write SetFlipHorizontal; + property FlipVertical: Boolean read GetFlipVertical write SetFlipVertical; + property ApplyDrawFullPathsInCenter: Boolean read GetApplyDrawFullPathsInCenter write SetApplyDrawFullPathsInCenter; + property SVGText: string read GetSVGText write SetSVGText; end; @@ -242,7 +261,7 @@ procedure TSVGIconFixedBitmapItem.DrawSVGIcon; LBitmapHeight := Round(FHeight * Scale); LBitmap.Width := LBitmapWidth; LBitmap.Height := LBitmapHeight; - PaintToBitmap(LBitmap, FSVG, FZoom); + PaintToBitmap(LBitmap, FSVG, FZoom, FSVG.KeepAspectRatio); if Assigned(FOwnerCollection) then FOwnerCollection.OnDrawImage(Self); end; @@ -305,6 +324,84 @@ function TSVGIconFixedBitmapItem.StoreOpacity: Boolean; { TSVGIconImage } +procedure TSVGIconImage.Assign(Source: TPersistent); +var + LSVG: TSVGIconImage; + LSVGBitmap: TSVGIconFixedBitmapItem; +begin + if Source is TSVGIconImage then begin + LSVG := TSVGIconImage(Source); + + MultiResBitmap.Assign(LSVG.MultiResBitmap); + Align:= LSVG.Align; + Anchors:= LSVG.Anchors; + BitmapMargins.Assign(LSVG.BitmapMargins); + ClipChildren := LSVG.ClipChildren; + ClipParent := LSVG.ClipParent; + Cursor := LSVG.Cursor; + DisableInterpolation := LSVG.DisableInterpolation; + DragMode := LSVG.DragMode; + EnableDragHighlight := LSVG.EnableDragHighlight; + Enabled := LSVG.Enabled; + Locked := LSVG.Locked; + Height := LSVG.Height; + Hint := LSVG.Hint; + HitTest := LSVG.HitTest; + Padding.Assign(LSVG.Padding); + MarginWrapMode := LSVG.MarginWrapMode; + Opacity := LSVG.Opacity; + Margins.Assign(LSVG.Margins); + if LSVG.PopupMenu <> nil then begin + if PopupMenu <> nil then + PopupMenu.Assign(LSVG.PopupMenu) + else + PopupMenu := LSVG.PopupMenu; + end else + PopupMenu := nil; + + RotationAngle := LSVG.RotationAngle; + RotationCenter.Assign(LSVG.RotationCenter); + Scale := LSVG.Scale; + Size.Assign(LSVG.Size); + Visible := LSVG.Visible; + Width := LSVG.Width; + WrapMode := LSVG.WrapMode; + ParentShowHint := LSVG.ParentShowHint; + ShowHint := LSVG.ShowHint; + + FZoom := LSVG.fZoom; + FSVGIconMultiResBitmap.Assign(LSVG.FSVGIconMultiResBitmap); + + Self.SVGText := LSVG.SVGText; + LSVGBitmap := Self.GetFixedBitmap; + + if LSVGBitmap <> nil then begin + var + LSourceFixedBitmap: TSVGIconFixedBitmapItem; + LSourceFixedBitmap := LSVG.GetFixedBitmap; + if Assigned(LSourceFixedBitmap) then begin + var + LSourceSVG: TFmxImageSVG; + LSourceSVG := LSourceFixedBitmap.SVG; + + if Assigned(LSourceSVG) then begin + LSVGBitmap.SVG.LoadFromText(LSVG.GetFixedBitmap.SVG.Source); + LSVGBitmap.SVG.FixedColor := LSVG.GetFixedBitmap.SVG.FixedColor; + LSVGBitmap.SVG.ApplyFixedColorToRootOnly := LSVG.GetFixedBitmap.SVG.ApplyFixedColorToRootOnly; + LSVGBitmap.SVG.GrayScale := LSVG.GetFixedBitmap.SVG.GrayScale; + LSVGBitmap.SVG.Opacity := LSVG.GetFixedBitmap.SVG.Opacity; + LSVGBitmap.SVG.KeepAspectRatio := LSVG.GetFixedBitmap.SVG.KeepAspectRatio; + LSVGBitmap.SVG.FlipVertical := LSVG.GetFixedBitmap.SVG.FlipVertical; + LSVGBitmap.SVG.FlipHorizontal := LSVG.GetFixedBitmap.SVG.FlipHorizontal; + LSVGBitmap.SVG.ApplyDrawFullPathsInCenter := LSVG.GetFixedBitmap.SVG.ApplyDrawFullPathsInCenter; + end; + + Self.Bitmap.Assign(LSVG.Bitmap); + end; + end; + end; +end; + constructor TSVGIconImage.Create(AOwner: TComponent); begin inherited; @@ -322,6 +419,24 @@ procedure TSVGIconImage.SetFixedColor(AColor: TAlphaColor); LItem.DrawSVGIcon; end; +procedure TSVGIconImage.SetFlipHorizontal(const Value: Boolean); +var + LItem: TSVGIconFixedBitmapItem; +begin + LItem := GetFixedBitmap; + LItem.SVG.FlipHorizontal := Value; + LItem.DrawSVGIcon; +end; + +procedure TSVGIconImage.SetFlipVertical(const Value: Boolean); +var + LItem: TSVGIconFixedBitmapItem; +begin + LItem := GetFixedBitmap; + LItem.SVG.FlipVertical := Value; + LItem.DrawSVGIcon; +end; + function TSVGIconImage.GetFixedColor: TAlphaColor; var LItem: TSVGIconFixedBitmapItem; @@ -330,6 +445,28 @@ function TSVGIconImage.GetFixedColor: TAlphaColor; Result := LItem.SVG.FixedColor; end; +function TSVGIconImage.GetFlipHorizontal: Boolean; +var + LItem: TSVGIconFixedBitmapItem; +begin + LItem := GetFixedBitmap; + if Assigned(LItem) then + Result := LItem.SVG.FlipHorizontal + else + Result := false; +end; + +function TSVGIconImage.GetFlipVertical: Boolean; +var + LItem: TSVGIconFixedBitmapItem; +begin + LItem := GetFixedBitmap; + if Assigned(LItem) then + Result := LItem.SVG.FlipVertical + else + Result := false; +end; + procedure TSVGIconImage.SetGrayScale(AValue: Boolean); var LItem: TSVGIconFixedBitmapItem; @@ -347,6 +484,26 @@ function TSVGIconImage.GetGrayScale: Boolean; Result := LItem.SVG.GrayScale; end; +function TSVGIconImage.GetKeepAspectRatio: Boolean; +var + LItem: TSVGIconFixedBitmapItem; +begin + LItem := GetFixedBitmap; + Result := LItem.SVG.KeepAspectRatio; +end; + +function TSVGIconImage.GetPathBounds: TRectF; +var + LItem: TSVGIconFixedBitmapItem; +begin + Result := TRectF.Empty; + LItem := GetFixedBitmap; + if Assigned(LItem) and Assigned(LItem.SVG) then begin + LItem.DrawSVGIcon; + Result := LItem.SVG.GetPathBounds; + end; +end; + procedure TSVGIconImage.SetSVGText(AValue: string); var LItem: TSVGIconFixedBitmapItem; @@ -375,6 +532,19 @@ destructor TSVGIconImage.Destroy; FSVGIconMultiResBitmap := nil; end; +function TSVGIconImage.DrawFullPathsInCenter: Boolean; +var + LItem: TSVGIconFixedBitmapItem; +begin + LItem := GetFixedBitmap; + if Assigned(LItem) and Assigned(LItem.SVG) then begin + LItem.DrawSVGIcon; // Must call DrawSVGIcon before DrawFullPathsInCenter; + Result := LItem.SVG.DrawFullPathsInCenter; + Exit; + end; + Result := false; +end; + procedure TSVGIconImage.SetIconSize(const AWidth, AHeight: Single; const AZoom: Integer); begin @@ -384,6 +554,25 @@ procedure TSVGIconImage.SetIconSize(const AWidth, AHeight: Single; FSVGIconMultiResBitmap.UpdateImageSize(AWidth, AHeight, AZoom); end; +procedure TSVGIconImage.SetKeepAspectRatio(const Value: Boolean); +var + LItem: TSVGIconFixedBitmapItem; +begin + LItem := GetFixedBitmap; + if LItem.SVG.KeepAspectRatio <> Value then begin + LItem.SVG.KeepAspectRatio := Value; + LItem.DrawSVGIcon; + end; +end; + +function TSVGIconImage.GetApplyDrawFullPathsInCenter: Boolean; +var + LItem: TSVGIconFixedBitmapItem; +begin + LItem := GetFixedBitmap; + Result := LItem.SVG.ApplyDrawFullPathsInCenter; +end; + function TSVGIconImage.GetFixedBitmap: TSVGIconFixedBitmapItem; begin Assert(MultiResBitmap.Count > 0); @@ -414,6 +603,24 @@ procedure TSVGIconImage.SetBounds(X, Y, AWidth, AHeight: Single); SetIconSize(AWidth, AHeight, FZoom); end; +procedure TSVGIconImage.SetApplyDrawFullPathsInCenter(const Value: Boolean); +var + LItem: TSVGIconFixedBitmapItem; +begin + LItem := GetFixedBitmap; + LItem.SVG.ApplyDrawFullPathsInCenter := Value; + if Value then begin + LItem.DrawSVGIcon; + end + else begin + var + LSVG := LItem.SVGText; + LItem.SVGText := ''; + LItem.SVGText := LSVG; + LItem.DrawSVGIcon; + end; +end; + procedure TSVGIconImage.SetBitmapZoom(const AValue: Integer); begin if (FZoom <> AValue) and (AValue <= 100) and (AValue >= 10) then From 90f1c1e61ee60d7c18b9c84e722d9b64343cb343 Mon Sep 17 00:00:00 2001 From: wqmeng Date: Sat, 23 Aug 2025 20:59:08 +0800 Subject: [PATCH 09/11] Add flip, CenterAllPaths in this VCL demo. --- Demo/SvgViewer/FrameViewer.pas | 43 ++++++++++ Demo/SvgViewer/SvgViewerUnit.dfm | 133 ++++++++++++++++++++++++++----- Demo/SvgViewer/SvgViewerUnit.pas | 59 +++++++++++++- 3 files changed, 214 insertions(+), 21 deletions(-) diff --git a/Demo/SvgViewer/FrameViewer.pas b/Demo/SvgViewer/FrameViewer.pas index 53d1a1a..2f531ab 100644 --- a/Demo/SvgViewer/FrameViewer.pas +++ b/Demo/SvgViewer/FrameViewer.pas @@ -25,6 +25,12 @@ TFrameView = class(TFrame) function GetApplyFixedColorToRootOnly: Boolean; procedure SetApplyFixedColorToRootOnly(const AValue: Boolean); procedure SetKeepAspectRatio(const AValue: Boolean); + procedure SetFlipHorizontal(const Value: Boolean); + procedure SetFlipVertically(const Value: Boolean); + function GetApplyDrawFullPathsInCenter: Boolean; + procedure SetApplyDrawFullPathsInCenter(const Value: Boolean); + function GetFlipHorizontal: Boolean; + function GetFlipVertically: Boolean; public procedure InitViewer(const ATitle: string; const SVGFactory : ISVGFactory); procedure DrawFile(const AFileName: string); @@ -33,6 +39,10 @@ TFrameView = class(TFrame) property FixedColor: TColor read GetFixedColor write SetFixedColor; property ApplyFixedColorToRootOnly: Boolean read GetApplyFixedColorToRootOnly write SetApplyFixedColorToRootOnly; property KeepAspectRatio: Boolean read FKeeAspectRatio write SetKeepAspectRatio; + property DrawFullPathsInCenter: Boolean read GetApplyDrawFullPathsInCenter write SetApplyDrawFullPathsInCenter; + property FlipHorizontal: Boolean read GetFlipHorizontal write SetFlipHorizontal; + property FlipVertically: Boolean read GetFlipVertically write SetFlipVertically; + property SVG: ISVG read FSVG; end; implementation @@ -61,11 +71,26 @@ function TFrameView.GetApplyFixedColorToRootOnly: Boolean; Result := FSVG.ApplyFixedColorToRootOnly; end; +function TFrameView.GetApplyDrawFullPathsInCenter: Boolean; +begin + Result := FSVG.ApplyDrawFullPathsInCenter; +end; + function TFrameView.GetFixedColor: TColor; begin Result := FSVG.FixedColor; end; +function TFrameView.GetFlipHorizontal: Boolean; +begin + Result := FSVG.FlipHorizontal; +end; + +function TFrameView.GetFlipVertically: Boolean; +begin + Result := FSVG.FlipVertically; +end; + function TFrameView.GetGrayScale: Boolean; begin Result := FSVG.GrayScale; @@ -93,12 +118,30 @@ procedure TFrameView.SetApplyFixedColorToRootOnly(const AValue: Boolean); SVGPaintBox.invalidate; end; +procedure TFrameView.SetApplyDrawFullPathsInCenter(const Value: Boolean); +begin + FSVG.ApplyDrawFullPathsInCenter := Value; + SVGPaintBox.invalidate; +end; + procedure TFrameView.SetFixedColor(const AValue: TColor); begin FSVG.FixedColor := AValue; SVGPaintBox.invalidate; end; +procedure TFrameView.SetFlipHorizontal(const Value: Boolean); +begin + FSVG.FlipHorizontal := Value; + SVGPaintBox.invalidate; +end; + +procedure TFrameView.SetFlipVertically(const Value: Boolean); +begin + FSVG.FlipVertically := Value; + SVGPaintBox.invalidate; +end; + procedure TFrameView.SetGrayScale(const AValue: Boolean); begin FSVG.GrayScale := AValue; diff --git a/Demo/SvgViewer/SvgViewerUnit.dfm b/Demo/SvgViewer/SvgViewerUnit.dfm index 91837d8..79aad77 100644 --- a/Demo/SvgViewer/SvgViewerUnit.dfm +++ b/Demo/SvgViewer/SvgViewerUnit.dfm @@ -2,8 +2,8 @@ object SVGViewerForm: TSVGViewerForm Left = 0 Top = 0 Caption = 'SVG Preview & Engine Comparison' - ClientHeight = 600 - ClientWidth = 800 + ClientHeight = 785 + ClientWidth = 1072 Color = clWhite DoubleBuffered = True Font.Charset = DEFAULT_CHARSET @@ -18,14 +18,14 @@ object SVGViewerForm: TSVGViewerForm Left = 0 Top = 0 Width = 200 - Height = 600 + Height = 785 Align = alLeft TabOrder = 0 object ListBox: TListBox Left = 1 Top = 42 Width = 198 - Height = 557 + Height = 742 Align = alClient ItemHeight = 13 TabOrder = 0 @@ -60,26 +60,38 @@ object SVGViewerForm: TSVGViewerForm end end object RightPanel: TPanel - Left = 500 + Left = 772 Top = 0 Width = 300 - Height = 600 + Height = 785 Align = alRight BevelOuter = bvNone TabOrder = 1 inline FrameViewSkia: TFrameView Left = 0 - Top = 300 + Top = 433 Width = 300 - Height = 300 + Height = 352 Align = alClient TabOrder = 0 + ExplicitTop = 300 + ExplicitHeight = 485 + inherited ClientPanel: TPanel + Height = 352 + StyleElements = [seFont, seClient, seBorder] + ExplicitHeight = 485 + inherited SVGPaintBox: TPaintBox + Height = 327 + ExplicitTop = 88 + ExplicitHeight = 396 + end + end end object ControlPanel: TPanel Left = 0 Top = 0 Width = 300 - Height = 300 + Height = 433 Align = alTop BevelOuter = bvNone Ctl3D = True @@ -121,7 +133,7 @@ object SVGViewerForm: TSVGViewerForm Left = 3 Top = 105 Width = 294 - Height = 57 + Height = 88 Align = alTop Caption = 'Aspect' TabOrder = 1 @@ -145,16 +157,47 @@ object SVGViewerForm: TSVGViewerForm TabOrder = 1 OnClick = GrayScaleCheckBoxClick end + object ChkDrawFullPathsInCenter: TCheckBox + Left = 124 + Top = 16 + Width = 157 + Height = 17 + Hint = 'Delphi Image32 engine only currently.' + Caption = 'Draw FullPaths In Center' + ParentShowHint = False + ShowHint = True + TabOrder = 2 + OnClick = ChkDrawFullPathsInCenterClick + end + object ChkFlipV: TCheckBox + Left = 196 + Top = 34 + Width = 99 + Height = 17 + Caption = 'Flip Vertically' + TabOrder = 3 + OnClick = ChkFlipVClick + end + object ChkFlipH: TCheckBox + Left = 91 + Top = 34 + Width = 102 + Height = 17 + Caption = 'Flip Horizontal' + TabOrder = 4 + OnClick = ChkFlipHClick + end end object OpacityGroupBox: TGroupBox AlignWithMargins = True Left = 3 - Top = 168 + Top = 199 Width = 294 Height = 57 Align = alTop Caption = 'Opacity:' TabOrder = 2 + ExplicitTop = 168 object OpacityTrackBar: TTrackBar Left = 2 Top = 16 @@ -193,31 +236,81 @@ object SVGViewerForm: TSVGViewerForm TabOrder = 3 StyleElements = [seBorder] end + object MemoSVG: TMemo + Left = 0 + Top = 259 + Width = 300 + Height = 174 + Align = alClient + ReadOnly = True + ScrollBars = ssBoth + TabOrder = 4 + end end end object ClientPanel: TPanel Left = 200 Top = 0 - Width = 300 - Height = 600 + Width = 572 + Height = 785 Align = alClient BevelOuter = bvNone TabOrder = 2 inline FrameViewerD2D: TFrameView Left = 0 - Top = 300 - Width = 300 - Height = 300 - Align = alClient + Top = 464 + Width = 572 + Height = 321 + Align = alBottom TabOrder = 0 + ExplicitTop = 464 + ExplicitWidth = 572 + ExplicitHeight = 321 + inherited ClientPanel: TPanel + Width = 572 + Height = 321 + StyleElements = [seFont, seClient, seBorder] + ExplicitWidth = 572 + ExplicitHeight = 321 + inherited SVGPaintBox: TPaintBox + Width = 570 + Height = 296 + ExplicitTop = 96 + ExplicitWidth = 571 + ExplicitHeight = 388 + end + inherited TitlePanel: TPanel + Width = 570 + ExplicitWidth = 570 + end + end end inline FrameViewImage32: TFrameView Left = 0 Top = 0 - Width = 300 - Height = 300 - Align = alTop + Width = 572 + Height = 464 + Align = alClient TabOrder = 1 + ExplicitWidth = 572 + ExplicitHeight = 464 + inherited ClientPanel: TPanel + Width = 572 + Height = 464 + StyleElements = [seFont, seClient, seBorder] + ExplicitWidth = 572 + ExplicitHeight = 464 + inherited SVGPaintBox: TPaintBox + Width = 570 + Height = 439 + ExplicitWidth = 614 + ExplicitHeight = 271 + end + inherited TitlePanel: TPanel + Width = 570 + ExplicitWidth = 570 + end + end end end object OpenDialog1: TOpenDialog diff --git a/Demo/SvgViewer/SvgViewerUnit.pas b/Demo/SvgViewer/SvgViewerUnit.pas index f83ddf6..ee3611b 100644 --- a/Demo/SvgViewer/SvgViewerUnit.pas +++ b/Demo/SvgViewer/SvgViewerUnit.pas @@ -31,6 +31,10 @@ TSVGViewerForm = class(TForm) OpacityGroupBox: TGroupBox; OpacityTrackBar: TTrackBar; TitlePanel: TPanel; + ChkDrawFullPathsInCenter: TCheckBox; + ChkFlipV: TCheckBox; + ChkFlipH: TCheckBox; + MemoSVG: TMemo; procedure FormCreate(Sender: TObject); procedure OpenButtonClick(Sender: TObject); procedure ListBoxClick(Sender: TObject); @@ -41,6 +45,9 @@ TSVGViewerForm = class(TForm) procedure GrayScaleCheckBoxClick(Sender: TObject); procedure ApplyToRootOnlyCheckBoxClick(Sender: TObject); procedure KeepCheckBoxClick(Sender: TObject); + procedure ChkDrawFullPathsInCenterClick(Sender: TObject); + procedure ChkFlipHClick(Sender: TObject); + procedure ChkFlipVClick(Sender: TObject); private FSourcePath: string; procedure DrawImage(const AFileName: string); @@ -62,6 +69,31 @@ implementation {$IFDEF SKIA}SkiaSVGFactory,{$ENDIF} D2DSVGFactory; +function CalculateCenteredViewBox(const ARect: TRect): string; +begin + Result := Format('%d %d %d %d', [ARect.Left, ARect.Top, ARect.Width, ARect.Height]); +end; + +function ReplaceViewBoxString(const ASVGText, ANewViewBox: string): string; +var + StartPos, EndPos: Integer; + Prefix, Suffix: string; +begin + StartPos := Pos('viewBox="', ASVGText); + if StartPos = 0 then + Exit(ASVGText); // viewBox not found, return original + + Inc(StartPos, Length('viewBox="')); + EndPos := StartPos; + while (EndPos <= Length(ASVGText)) and (ASVGText[EndPos] <> '"') do + Inc(EndPos); + + Prefix := Copy(ASVGText, 1, StartPos - Length('viewBox="') - 1); + Suffix := Copy(ASVGText, EndPos + 1, MaxInt); + + Result := Prefix + 'viewBox="' + ANewViewBox + '"' + Suffix; +end; + procedure TSVGViewerForm.ApplyToRootOnlyCheckBoxClick(Sender: TObject); begin FrameViewerD2D.ApplyFixedColorToRootOnly := ApplyToRootOnlyCheckBox.Checked; @@ -69,11 +101,37 @@ procedure TSVGViewerForm.ApplyToRootOnlyCheckBoxClick(Sender: TObject); FrameViewImage32.ApplyFixedColorToRootOnly := ApplyToRootOnlyCheckBox.Checked; end; +procedure TSVGViewerForm.ChkDrawFullPathsInCenterClick(Sender: TObject); +begin + FrameViewerD2D.DrawFullPathsInCenter := chkDrawFullPathsInCenter.Checked; + {$IFDEF SKIA}FrameViewSkia.DrawFullPathsInCenter := chkDrawFullPathsInCenter.Checked;{$ENDIF} + FrameViewImage32.DrawFullPathsInCenter := chkDrawFullPathsInCenter.Checked; +end; + +procedure TSVGViewerForm.ChkFlipHClick(Sender: TObject); +begin + FrameViewerD2D.FlipHorizontal := chkFlipH.Checked; + {$IFDEF SKIA}FrameViewSkia.FlipHorizontal := chkFlipH.Checked;{$ENDIF} + FrameViewImage32.FlipHorizontal := chkFlipH.Checked; + FrameViewImage32.Repaint; +end; + +procedure TSVGViewerForm.ChkFlipVClick(Sender: TObject); +begin + FrameViewerD2D.FlipVertically := chkFlipV.Checked; + {$IFDEF SKIA}FrameViewSkia.FlipVertically := chkFlipV.Checked;{$ENDIF} + FrameViewImage32.FlipVertically := chkFlipV.Checked; + FrameViewImage32.Repaint; +end; + procedure TSVGViewerForm.DrawImage(const AFileName: string); begin FrameViewerD2D.DrawFile(AFileName); {$IFDEF SKIA}FrameViewSkia.DrawFile(AFileName);{$ENDIF} FrameViewImage32.DrawFile(AFileName); + + if Assigned(MemoSVG) then + MemoSVG.Text := FrameViewImage32.SVG.Source; end; procedure TSVGViewerForm.OpacityTrackBarChange(Sender: TObject); @@ -144,7 +202,6 @@ procedure TSVGViewerForm.FormResize(Sender: TObject); LHeight, LWidth: Integer; begin LHeight := RightPanel.ClientHeight div 2; - FrameViewImage32.Height := LHeight; FrameViewerD2D.Height := LHeight; ControlPanel.Height := LHeight; LWidth := (Self.ClientWidth - FilesPanel.Width) div 2; From c92dc01eee8177f88180ca2e129c53f1752d9be5 Mon Sep 17 00:00:00 2001 From: wqmeng Date: Mon, 25 Aug 2025 13:07:11 +0800 Subject: [PATCH 10/11] Remove unused var. --- Source/FMX.Image32SVG.pas | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/FMX.Image32SVG.pas b/Source/FMX.Image32SVG.pas index 8184c94..0a46339 100644 --- a/Source/FMX.Image32SVG.pas +++ b/Source/FMX.Image32SVG.pas @@ -103,8 +103,6 @@ destructor TFmxImage32SVG.Destroy; end; function TFmxImage32SVG.DrawFullPathsInCenter: Boolean; -var - LRect: TRect; begin Result := false; From 7d6cbf1f1dafcc0a8460d97dfd36e7757f7059d9 Mon Sep 17 00:00:00 2001 From: wqmeng Date: Mon, 25 Aug 2025 13:10:24 +0800 Subject: [PATCH 11/11] Fixed the issue of blurry display after copying --- Source/FMX.SVGIconImage.pas | 114 ++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 58 deletions(-) diff --git a/Source/FMX.SVGIconImage.pas b/Source/FMX.SVGIconImage.pas index 185df9a..218748e 100644 --- a/Source/FMX.SVGIconImage.pas +++ b/Source/FMX.SVGIconImage.pas @@ -326,77 +326,75 @@ function TSVGIconFixedBitmapItem.StoreOpacity: Boolean; procedure TSVGIconImage.Assign(Source: TPersistent); var - LSVG: TSVGIconImage; - LSVGBitmap: TSVGIconFixedBitmapItem; + LSrcSVGImg: TSVGIconImage; + LFixedBitmap: TSVGIconFixedBitmapItem; begin if Source is TSVGIconImage then begin - LSVG := TSVGIconImage(Source); - - MultiResBitmap.Assign(LSVG.MultiResBitmap); - Align:= LSVG.Align; - Anchors:= LSVG.Anchors; - BitmapMargins.Assign(LSVG.BitmapMargins); - ClipChildren := LSVG.ClipChildren; - ClipParent := LSVG.ClipParent; - Cursor := LSVG.Cursor; - DisableInterpolation := LSVG.DisableInterpolation; - DragMode := LSVG.DragMode; - EnableDragHighlight := LSVG.EnableDragHighlight; - Enabled := LSVG.Enabled; - Locked := LSVG.Locked; - Height := LSVG.Height; - Hint := LSVG.Hint; - HitTest := LSVG.HitTest; - Padding.Assign(LSVG.Padding); - MarginWrapMode := LSVG.MarginWrapMode; - Opacity := LSVG.Opacity; - Margins.Assign(LSVG.Margins); - if LSVG.PopupMenu <> nil then begin + LSrcSVGImg := TSVGIconImage(Source); + Align:= LSrcSVGImg.Align; + SetBounds(Self.Position.X, Self.Position.Y, LSrcSVGImg.Width, LSrcSVGImg.Height); // Must call this avoid image stretch problem. + Anchors:= LSrcSVGImg.Anchors; + BitmapMargins.Assign(LSrcSVGImg.BitmapMargins); + ClipChildren := LSrcSVGImg.ClipChildren; + ClipParent := LSrcSVGImg.ClipParent; + Cursor := LSrcSVGImg.Cursor; + DisableInterpolation := LSrcSVGImg.DisableInterpolation; + DragMode := LSrcSVGImg.DragMode; + EnableDragHighlight := LSrcSVGImg.EnableDragHighlight; + Enabled := LSrcSVGImg.Enabled; + Locked := LSrcSVGImg.Locked; + Hint := LSrcSVGImg.Hint; + HitTest := LSrcSVGImg.HitTest; + Padding.Assign(LSrcSVGImg.Padding); + MarginWrapMode := LSrcSVGImg.MarginWrapMode; + Opacity := LSrcSVGImg.Opacity; + Margins.Assign(LSrcSVGImg.Margins); + if LSrcSVGImg.PopupMenu <> nil then begin if PopupMenu <> nil then - PopupMenu.Assign(LSVG.PopupMenu) + PopupMenu.Assign(LSrcSVGImg.PopupMenu) else - PopupMenu := LSVG.PopupMenu; + PopupMenu := LSrcSVGImg.PopupMenu; end else PopupMenu := nil; - RotationAngle := LSVG.RotationAngle; - RotationCenter.Assign(LSVG.RotationCenter); - Scale := LSVG.Scale; - Size.Assign(LSVG.Size); - Visible := LSVG.Visible; - Width := LSVG.Width; - WrapMode := LSVG.WrapMode; - ParentShowHint := LSVG.ParentShowHint; - ShowHint := LSVG.ShowHint; + RotationAngle := LSrcSVGImg.RotationAngle; + RotationCenter.Assign(LSrcSVGImg.RotationCenter); + Scale := LSrcSVGImg.Scale; + Size.Assign(LSrcSVGImg.Size); + Visible := LSrcSVGImg.Visible; + WrapMode := LSrcSVGImg.WrapMode; + ParentShowHint := LSrcSVGImg.ParentShowHint; + ShowHint := LSrcSVGImg.ShowHint; - FZoom := LSVG.fZoom; - FSVGIconMultiResBitmap.Assign(LSVG.FSVGIconMultiResBitmap); + FZoom := LSrcSVGImg.fZoom; - Self.SVGText := LSVG.SVGText; - LSVGBitmap := Self.GetFixedBitmap; - - if LSVGBitmap <> nil then begin + Self.SVGText := LSrcSVGImg.SVGText; + LFixedBitmap := Self.GetFixedBitmap; + if LFixedBitmap <> nil then begin var - LSourceFixedBitmap: TSVGIconFixedBitmapItem; - LSourceFixedBitmap := LSVG.GetFixedBitmap; - if Assigned(LSourceFixedBitmap) then begin + LSrcFixedBitmap: TSVGIconFixedBitmapItem; + LSrcFixedBitmap := LSrcSVGImg.GetFixedBitmap; + if Assigned(LSrcFixedBitmap) then begin + LFixedBitmap.Assign(LSrcFixedBitmap); var - LSourceSVG: TFmxImageSVG; - LSourceSVG := LSourceFixedBitmap.SVG; - - if Assigned(LSourceSVG) then begin - LSVGBitmap.SVG.LoadFromText(LSVG.GetFixedBitmap.SVG.Source); - LSVGBitmap.SVG.FixedColor := LSVG.GetFixedBitmap.SVG.FixedColor; - LSVGBitmap.SVG.ApplyFixedColorToRootOnly := LSVG.GetFixedBitmap.SVG.ApplyFixedColorToRootOnly; - LSVGBitmap.SVG.GrayScale := LSVG.GetFixedBitmap.SVG.GrayScale; - LSVGBitmap.SVG.Opacity := LSVG.GetFixedBitmap.SVG.Opacity; - LSVGBitmap.SVG.KeepAspectRatio := LSVG.GetFixedBitmap.SVG.KeepAspectRatio; - LSVGBitmap.SVG.FlipVertical := LSVG.GetFixedBitmap.SVG.FlipVertical; - LSVGBitmap.SVG.FlipHorizontal := LSVG.GetFixedBitmap.SVG.FlipHorizontal; - LSVGBitmap.SVG.ApplyDrawFullPathsInCenter := LSVG.GetFixedBitmap.SVG.ApplyDrawFullPathsInCenter; + LSVG, LSrcSVG: TFmxImageSVG; + LSrcSVG := LSrcFixedBitmap.SVG; + LSVG := LFixedBitmap.SVG; + if Assigned(LSVG) and Assigned(LSrcSVG) then begin + LSVG.FixedColor := LSrcSVG.FixedColor; + LSVG.ApplyFixedColorToRootOnly := LSrcSVG.ApplyFixedColorToRootOnly; + LSVG.GrayScale := LSrcSVG.GrayScale; + LSVG.Opacity := LSrcSVG.Opacity; + LSVG.KeepAspectRatio := LSrcSVG.KeepAspectRatio; + LSVG.FlipVertical := LSrcSVG.FlipVertical; + LSVG.FlipHorizontal := LSrcSVG.FlipHorizontal; + LSVG.ApplyDrawFullPathsInCenter := LSrcSVG.ApplyDrawFullPathsInCenter; + var + LSource: String; + + LSVG.LoadFromText(LSrcSVG.Source); end; - Self.Bitmap.Assign(LSVG.Bitmap); end; end; end;