diff --git a/samples/AvaloniaSvgSkiaSample/MainWindow.axaml b/samples/AvaloniaSvgSkiaSample/MainWindow.axaml index 9a00752507..d97eae9681 100644 --- a/samples/AvaloniaSvgSkiaSample/MainWindow.axaml +++ b/samples/AvaloniaSvgSkiaSample/MainWindow.axaml @@ -189,6 +189,19 @@ + + + + + + + + + diff --git a/samples/AvaloniaSvgSkiaSample/MainWindow.axaml.cs b/samples/AvaloniaSvgSkiaSample/MainWindow.axaml.cs index 332016a50b..9c7d2b129b 100644 --- a/samples/AvaloniaSvgSkiaSample/MainWindow.axaml.cs +++ b/samples/AvaloniaSvgSkiaSample/MainWindow.axaml.cs @@ -1,13 +1,11 @@ -using System; -using System.IO; +using System.IO; using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Input; -using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Svg.Skia; -using SS = Svg.Skia; +using ShimSkiaSharp; namespace AvaloniaSvgSkiaSample; @@ -37,6 +35,32 @@ public MainWindow() """; + + var svgImage = svgCloningOriginal.Source as SvgImage; + var clone = new SvgImage { Source = svgImage?.Source?.Clone() }; + + foreach (var cmd in clone.Source?.Svg?.Model?.Commands?.OfType() ?? []) + { + var paint = cmd.Paint; + if (paint?.Color is not null) + { + paint.Color = ToGrayscale(paint.Color.Value); + } + + if (paint?.Shader is ColorShader shader) + { + paint.Shader = SKShader.CreateColor(ToGrayscale(paint.Color.Value), shader.ColorSpace); + } + } + + clone.Source?.RebuildFromModel(); + svgCloningClone.Source = clone; + } + + private static SKColor ToGrayscale(SKColor color) + { + var luminance = (byte)(0.2126f * color.Red + 0.7152f * color.Green + 0.0722f * color.Blue); + return new SKColor(luminance, luminance, luminance, color.Alpha); } public void SvgSvgStretchChanged(object sender, SelectionChangedEventArgs e) diff --git a/src/ShimSkiaSharp/SKPictureExtensions.cs b/src/ShimSkiaSharp/SKPictureExtensions.cs new file mode 100644 index 0000000000..c949856a17 --- /dev/null +++ b/src/ShimSkiaSharp/SKPictureExtensions.cs @@ -0,0 +1,478 @@ +// Copyright (c) Stefan Koell. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace ShimSkiaSharp; + +public static class SKPictureExtensions +{ + public static SKPicture Clone(this SKPicture picture) + { + var cache = new Dictionary(new ReferenceComparer()); + return ClonePicture(picture, cache); + } + + private static SKPicture ClonePicture(SKPicture picture, Dictionary cache) + { + if (cache.TryGetValue(picture, out var cached)) + { + return cached; + } + + var commands = picture.Commands is null ? null : new List(picture.Commands.Count); + var clone = new SKPicture(picture.CullRect, commands); + cache[picture] = clone; + + if (commands is null) + { + return clone; + } + + foreach (var command in picture.Commands!) + { + commands.Add(CloneCommand(command, cache)); + } + + return clone; + } + + private static CanvasCommand CloneCommand(CanvasCommand command, Dictionary cache) + { + return command switch + { + ClipPathCanvasCommand clipPathCommand => new ClipPathCanvasCommand(CloneClipPath(clipPathCommand.ClipPath, cache), clipPathCommand.Operation, clipPathCommand.Antialias), + ClipRectCanvasCommand clipRectCommand => new ClipRectCanvasCommand(clipRectCommand.Rect, clipRectCommand.Operation, clipRectCommand.Antialias), + DrawImageCanvasCommand drawImageCommand => new DrawImageCanvasCommand(CloneImage(drawImageCommand.Image), drawImageCommand.Source, drawImageCommand.Dest, ClonePaint(drawImageCommand.Paint, cache)), + DrawPathCanvasCommand drawPathCommand => new DrawPathCanvasCommand(ClonePath(drawPathCommand.Path), ClonePaint(drawPathCommand.Paint, cache)), + DrawTextBlobCanvasCommand drawTextBlobCommand => new DrawTextBlobCanvasCommand(CloneTextBlob(drawTextBlobCommand.TextBlob), drawTextBlobCommand.X, drawTextBlobCommand.Y, ClonePaint(drawTextBlobCommand.Paint, cache)), + DrawTextCanvasCommand drawTextCommand => new DrawTextCanvasCommand(drawTextCommand.Text, drawTextCommand.X, drawTextCommand.Y, ClonePaint(drawTextCommand.Paint, cache)), + DrawTextOnPathCanvasCommand drawTextOnPathCommand => new DrawTextOnPathCanvasCommand(drawTextOnPathCommand.Text, ClonePath(drawTextOnPathCommand.Path), drawTextOnPathCommand.HOffset, drawTextOnPathCommand.VOffset, ClonePaint(drawTextOnPathCommand.Paint, cache)), + RestoreCanvasCommand restoreCommand => new RestoreCanvasCommand(restoreCommand.Count), + SaveCanvasCommand saveCommand => new SaveCanvasCommand(saveCommand.Count), + SaveLayerCanvasCommand saveLayerCommand => new SaveLayerCanvasCommand(saveLayerCommand.Count, ClonePaint(saveLayerCommand.Paint, cache)), + SetMatrixCanvasCommand setMatrixCommand => new SetMatrixCanvasCommand(setMatrixCommand.DeltaMatrix, setMatrixCommand.TotalMatrix), + _ => command + }; + } + + private static SKPaint? ClonePaint(SKPaint? paint, Dictionary cache) + { + if (paint is null) + { + return null; + } + + var clone = paint.Clone(); + clone.Shader = CloneShader(paint.Shader, cache); + clone.ColorFilter = CloneColorFilter(paint.ColorFilter); + clone.ImageFilter = CloneImageFilter(paint.ImageFilter, cache); + clone.PathEffect = ClonePathEffect(paint.PathEffect); + return clone; + } + + private static SKPath? ClonePath(SKPath? path) + { + if (path is null) + { + return null; + } + + var clone = new SKPath + { + FillType = path.FillType + }; + + if (path.Commands is null) + { + return clone; + } + + foreach (var command in path.Commands) + { + ClonePathCommand(clone, command); + } + + return clone; + } + + private static void ClonePathCommand(SKPath target, PathCommand command) + { + switch (command) + { + case AddCirclePathCommand addCircle: + target.AddCircle(addCircle.X, addCircle.Y, addCircle.Radius); + return; + case AddOvalPathCommand addOval: + target.AddOval(addOval.Rect); + return; + case AddPolyPathCommand addPoly: + target.AddPoly(ClonePoints(addPoly.Points), addPoly.Close); + return; + case AddRectPathCommand addRect: + target.AddRect(addRect.Rect); + return; + case AddRoundRectPathCommand addRoundRect: + target.AddRoundRect(addRoundRect.Rect, addRoundRect.Rx, addRoundRect.Ry); + return; + case ArcToPathCommand arcTo: + target.ArcTo(arcTo.Rx, arcTo.Ry, arcTo.XAxisRotate, arcTo.LargeArc, arcTo.Sweep, arcTo.X, arcTo.Y); + return; + case ClosePathCommand: + target.Close(); + return; + case CubicToPathCommand cubicTo: + target.CubicTo(cubicTo.X0, cubicTo.Y0, cubicTo.X1, cubicTo.Y1, cubicTo.X2, cubicTo.Y2); + return; + case LineToPathCommand lineTo: + target.LineTo(lineTo.X, lineTo.Y); + return; + case MoveToPathCommand moveTo: + target.MoveTo(moveTo.X, moveTo.Y); + return; + case QuadToPathCommand quadTo: + target.QuadTo(quadTo.X0, quadTo.Y0, quadTo.X1, quadTo.Y1); + return; + } + } + + private static SKPoint[]? ClonePoints(IList? points) + { + if (points is null) + { + return null; + } + + var copy = new SKPoint[points.Count]; + for (var i = 0; i < points.Count; i++) + { + copy[i] = points[i]; + } + return copy; + } + + private static SKImage? CloneImage(SKImage? image) + { + if (image is null) + { + return null; + } + + return new SKImage + { + Data = image.Data is null ? null : (byte[])image.Data.Clone(), + Width = image.Width, + Height = image.Height + }; + } + + private static SKTextBlob? CloneTextBlob(SKTextBlob? textBlob) + { + if (textBlob is null) + { + return null; + } + + var points = textBlob.Points is null ? null : (SKPoint[])textBlob.Points.Clone(); + return SKTextBlob.CreatePositioned(textBlob.Text, points); + } + + private static ClipPath? CloneClipPath(ClipPath? clipPath, Dictionary cache) + { + if (clipPath is null) + { + return null; + } + + var clone = new ClipPath + { + Transform = clipPath.Transform, + Clip = CloneClipPath(clipPath.Clip, cache) + }; + + if (clipPath.Clips is null) + { + return clone; + } + + var clips = new List(clipPath.Clips.Count); + foreach (var clip in clipPath.Clips) + { + clips.Add(ClonePathClip(clip, cache)); + } + clone.Clips = clips; + + return clone; + } + + private static PathClip ClonePathClip(PathClip pathClip, Dictionary cache) + { + return new PathClip + { + Path = ClonePath(pathClip.Path), + Transform = pathClip.Transform, + Clip = CloneClipPath(pathClip.Clip, cache) + }; + } + + private static SKShader? CloneShader(SKShader? shader, Dictionary cache) + { + switch (shader) + { + case null: + return null; + case ColorShader colorShader: + return new ColorShader(colorShader.Color, colorShader.ColorSpace); + case LinearGradientShader linearGradient: + return new LinearGradientShader( + linearGradient.Start, + linearGradient.End, + linearGradient.Colors is null ? null : (SKColorF[])linearGradient.Colors.Clone(), + linearGradient.ColorSpace, + linearGradient.ColorPos is null ? null : (float[])linearGradient.ColorPos.Clone(), + linearGradient.Mode, + linearGradient.LocalMatrix); + case PerlinNoiseFractalNoiseShader perlinFractal: + return new PerlinNoiseFractalNoiseShader( + perlinFractal.BaseFrequencyX, + perlinFractal.BaseFrequencyY, + perlinFractal.NumOctaves, + perlinFractal.Seed, + perlinFractal.TileSize); + case PerlinNoiseTurbulenceShader perlinTurbulence: + return new PerlinNoiseTurbulenceShader( + perlinTurbulence.BaseFrequencyX, + perlinTurbulence.BaseFrequencyY, + perlinTurbulence.NumOctaves, + perlinTurbulence.Seed, + perlinTurbulence.TileSize); + case PictureShader pictureShader: + return new PictureShader( + pictureShader.Src is null ? null : ClonePicture(pictureShader.Src, cache), + pictureShader.TmX, + pictureShader.TmY, + pictureShader.LocalMatrix, + pictureShader.Tile); + case RadialGradientShader radialGradient: + return new RadialGradientShader( + radialGradient.Center, + radialGradient.Radius, + radialGradient.Colors is null ? null : (SKColorF[])radialGradient.Colors.Clone(), + radialGradient.ColorSpace, + radialGradient.ColorPos is null ? null : (float[])radialGradient.ColorPos.Clone(), + radialGradient.Mode, + radialGradient.LocalMatrix); + case TwoPointConicalGradientShader twoPoint: + return new TwoPointConicalGradientShader( + twoPoint.Start, + twoPoint.StartRadius, + twoPoint.End, + twoPoint.EndRadius, + twoPoint.Colors is null ? null : (SKColorF[])twoPoint.Colors.Clone(), + twoPoint.ColorSpace, + twoPoint.ColorPos is null ? null : (float[])twoPoint.ColorPos.Clone(), + twoPoint.Mode, + twoPoint.LocalMatrix); + default: + return shader; + } + } + + private static SKColorFilter? CloneColorFilter(SKColorFilter? colorFilter) + { + return colorFilter switch + { + null => null, + BlendModeColorFilter blendMode => new BlendModeColorFilter(blendMode.Color, blendMode.Mode), + ColorMatrixColorFilter matrix => new ColorMatrixColorFilter(matrix.Matrix is null ? null : (float[])matrix.Matrix.Clone()), + LumaColorColorFilter => new LumaColorColorFilter(), + TableColorFilter table => new TableColorFilter( + table.TableA is null ? null : (byte[])table.TableA.Clone(), + table.TableR is null ? null : (byte[])table.TableR.Clone(), + table.TableG is null ? null : (byte[])table.TableG.Clone(), + table.TableB is null ? null : (byte[])table.TableB.Clone()), + _ => colorFilter + }; + } + + private static SKImageFilter? CloneImageFilter(SKImageFilter? imageFilter, Dictionary cache) + { + switch (imageFilter) + { + case null: + return null; + case ArithmeticImageFilter arithmetic: + return new ArithmeticImageFilter( + arithmetic.K1, + arithmetic.K2, + arithmetic.K3, + arithmetic.K4, + arithmetic.EforcePMColor, + CloneImageFilter(arithmetic.Background, cache), + CloneImageFilter(arithmetic.Foreground, cache), + arithmetic.Clip); + case BlendModeImageFilter blendMode: + return new BlendModeImageFilter( + blendMode.Mode, + CloneImageFilter(blendMode.Background, cache), + CloneImageFilter(blendMode.Foreground, cache), + blendMode.Clip); + case BlurImageFilter blur: + return new BlurImageFilter( + blur.SigmaX, + blur.SigmaY, + CloneImageFilter(blur.Input, cache), + blur.Clip); + case ColorFilterImageFilter colorFilter: + return new ColorFilterImageFilter( + CloneColorFilter(colorFilter.ColorFilter), + CloneImageFilter(colorFilter.Input, cache), + colorFilter.Clip); + case DilateImageFilter dilate: + return new DilateImageFilter( + dilate.RadiusX, + dilate.RadiusY, + CloneImageFilter(dilate.Input, cache), + dilate.Clip); + case DisplacementMapEffectImageFilter displacement: + return new DisplacementMapEffectImageFilter( + displacement.XChannelSelector, + displacement.YChannelSelector, + displacement.Scale, + CloneImageFilter(displacement.Displacement, cache), + CloneImageFilter(displacement.Input, cache), + displacement.Clip); + case DistantLitDiffuseImageFilter distantDiffuse: + return new DistantLitDiffuseImageFilter( + distantDiffuse.Direction, + distantDiffuse.LightColor, + distantDiffuse.SurfaceScale, + distantDiffuse.Kd, + CloneImageFilter(distantDiffuse.Input, cache), + distantDiffuse.Clip); + case DistantLitSpecularImageFilter distantSpecular: + return new DistantLitSpecularImageFilter( + distantSpecular.Direction, + distantSpecular.LightColor, + distantSpecular.SurfaceScale, + distantSpecular.Ks, + distantSpecular.Shininess, + CloneImageFilter(distantSpecular.Input, cache), + distantSpecular.Clip); + case ErodeImageFilter erode: + return new ErodeImageFilter( + erode.RadiusX, + erode.RadiusY, + CloneImageFilter(erode.Input, cache), + erode.Clip); + case ImageImageFilter image: + return new ImageImageFilter( + CloneImage(image.Image), + image.Src, + image.Dst, + image.FilterQuality); + case MatrixConvolutionImageFilter matrix: + return new MatrixConvolutionImageFilter( + matrix.KernelSize, + matrix.Kernel is null ? null : (float[])matrix.Kernel.Clone(), + matrix.Gain, + matrix.Bias, + matrix.KernelOffset, + matrix.TileMode, + matrix.ConvolveAlpha, + CloneImageFilter(matrix.Input, cache), + matrix.Clip); + case MergeImageFilter merge: + return new MergeImageFilter(CloneImageFilters(merge.Filters, cache), merge.Clip); + case OffsetImageFilter offset: + return new OffsetImageFilter( + offset.Dx, + offset.Dy, + CloneImageFilter(offset.Input, cache), + offset.Clip); + case PaintImageFilter paint: + return new PaintImageFilter(ClonePaint(paint.Paint, cache), paint.Clip); + case ShaderImageFilter shader: + return new ShaderImageFilter(CloneShader(shader.Shader, cache), shader.Dither, shader.Clip); + case PictureImageFilter picture: + return new PictureImageFilter(picture.Picture is null ? null : ClonePicture(picture.Picture, cache), picture.Clip); + case PointLitDiffuseImageFilter pointDiffuse: + return new PointLitDiffuseImageFilter( + pointDiffuse.Location, + pointDiffuse.LightColor, + pointDiffuse.SurfaceScale, + pointDiffuse.Kd, + CloneImageFilter(pointDiffuse.Input, cache), + pointDiffuse.Clip); + case PointLitSpecularImageFilter pointSpecular: + return new PointLitSpecularImageFilter( + pointSpecular.Location, + pointSpecular.LightColor, + pointSpecular.SurfaceScale, + pointSpecular.Ks, + pointSpecular.Shininess, + CloneImageFilter(pointSpecular.Input, cache), + pointSpecular.Clip); + case SpotLitDiffuseImageFilter spotDiffuse: + return new SpotLitDiffuseImageFilter( + spotDiffuse.Location, + spotDiffuse.Target, + spotDiffuse.SpecularExponent, + spotDiffuse.CutoffAngle, + spotDiffuse.LightColor, + spotDiffuse.SurfaceScale, + spotDiffuse.Kd, + CloneImageFilter(spotDiffuse.Input, cache), + spotDiffuse.Clip); + case SpotLitSpecularImageFilter spotSpecular: + return new SpotLitSpecularImageFilter( + spotSpecular.Location, + spotSpecular.Target, + spotSpecular.SpecularExponent, + spotSpecular.CutoffAngle, + spotSpecular.LightColor, + spotSpecular.SurfaceScale, + spotSpecular.Ks, + spotSpecular.Shininess, + CloneImageFilter(spotSpecular.Input, cache), + spotSpecular.Clip); + case TileImageFilter tile: + return new TileImageFilter(tile.Src, tile.Dst, CloneImageFilter(tile.Input, cache)); + default: + return imageFilter; + } + } + + private static SKImageFilter[]? CloneImageFilters(SKImageFilter[]? filters, Dictionary cache) + { + if (filters is null) + { + return null; + } + + var clone = new SKImageFilter[filters.Length]; + for (var i = 0; i < filters.Length; i++) + { + clone[i] = CloneImageFilter(filters[i], cache)!; + } + return clone; + } + + private static SKPathEffect? ClonePathEffect(SKPathEffect? pathEffect) + { + return pathEffect switch + { + null => null, + DashPathEffect dash => new DashPathEffect(dash.Intervals is null ? null : (float[])dash.Intervals.Clone(), dash.Phase), + _ => pathEffect + }; + } + + private sealed class ReferenceComparer : IEqualityComparer + { + public bool Equals(SKPicture? x, SKPicture? y) => ReferenceEquals(x, y); + + public int GetHashCode(SKPicture obj) => RuntimeHelpers.GetHashCode(obj); + } +} diff --git a/src/Svg.Controls.Skia.Avalonia/SvgSource.cs b/src/Svg.Controls.Skia.Avalonia/SvgSource.cs index cd4635e1ea..67d5a8a246 100644 --- a/src/Svg.Controls.Skia.Avalonia/SvgSource.cs +++ b/src/Svg.Controls.Skia.Avalonia/SvgSource.cs @@ -1,5 +1,6 @@ -// Copyright (c) Wiesław Šoltés. All rights reserved. +// Copyright (c) Wiesław Šoltés, Stefan Koell. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. + using System; using System.Collections.Generic; using System.ComponentModel; @@ -7,10 +8,10 @@ using System.IO; using System.Net.Http; using Avalonia.Metadata; +using Avalonia.Platform; using SkiaSharp; using Svg; using Svg.Model; -using Svg.Model.Services; using Svg.Skia; namespace Avalonia.Svg.Skia; @@ -153,19 +154,27 @@ static SvgSource() return skSvg.Picture; } - private static SKPicture? FromSvg(string svg) + private static SKPicture? FromSvg(SvgSource source, string svg) { var skSvg = new SKSvg(); skSvg.FromSvg(svg); + lock (source.Sync) + { + source._skSvg = skSvg; + } return skSvg.Picture; } - private static SKPicture? FromSvgDocument(SvgDocument? svgDocument) + private static SKPicture? FromSvgDocument(SvgSource source, SvgDocument? svgDocument) { if (svgDocument is { }) { var skSvg = new SKSvg(); skSvg.FromSvgDocument(svgDocument); + lock (source.Sync) + { + source._skSvg = skSvg; + } return skSvg.Picture; } return null; @@ -213,7 +222,7 @@ static SvgSource() } else { - var stream = Platform.AssetLoader.Open(uri, baseUri); + var stream = AssetLoader.Open(uri, baseUri); if (stream is null) { ThrowOnMissingResource(path); @@ -245,12 +254,7 @@ public static SvgSource Load(string path, Uri? baseUri = default, SvgParameters? public static SvgSource LoadFromSvg(string svg) { var source = new SvgSource(default(Uri)); - source._picture = FromSvg(svg); - // loading from SVG string does not store SKSvg instance - lock (source.Sync) - { - source._skSvg = null; - } + source._picture = FromSvg(source, svg); return source; } @@ -275,14 +279,63 @@ public static SvgSource LoadFromStream(Stream stream, SvgParameters? parameters public static SvgSource LoadFromSvgDocument(SvgDocument document) { var source = new SvgSource(default(Uri)); - source._picture = FromSvgDocument(document); - lock (source.Sync) + source._picture = FromSvgDocument(source, document); + return source; + } + + /// + /// Creates a new instance of the class that is a copy of the current instance. + /// + /// A new object that is a clone of this instance. + public SvgSource Clone() + { + lock (Sync) { - source._skSvg = null; + var clone = new SvgSource(_baseUri) + { + Path = Path, + Entities = Entities is null ? null : new Dictionary(Entities), + Css = Css + }; + + clone._originalParameters = _originalParameters; + clone._originalPath = _originalPath; + if (_originalStream is { }) + { + clone._originalStream = new MemoryStream(); + _originalStream.Position = 0; + _originalStream.CopyTo(clone._originalStream); + } + + if (_skSvg is { }) + { + var skSvgClone = _skSvg.Clone(); + clone._skSvg = skSvgClone; + } + + return clone; + } + } + + /// + /// Rebuilds the from its underlying model, refreshing its associated + /// representation if the instance exists. + /// + public void RebuildFromModel() + { + if (Svg is null) + return; + lock (Sync) + { + Svg.Picture = Svg.SkiaModel.ToSKPicture(Svg.Model); + _picture = Svg.Picture; } - return source; } + /// + /// Reloads the SVG from the original source using the provided parameters. Modifications on the are lost. + /// + /// The optional parameters that define entities and CSS to apply when reloading the SVG source. public void ReLoad(SvgParameters? parameters) { lock (Sync) diff --git a/src/Svg.Skia/SKSvg.Model.cs b/src/Svg.Skia/SKSvg.Model.cs index 782cd7e916..bc7e9efe04 100644 --- a/src/Svg.Skia/SKSvg.Model.cs +++ b/src/Svg.Skia/SKSvg.Model.cs @@ -7,6 +7,7 @@ using Svg.Model; using Svg.Model.Drawables.Factories; using Svg.Model.Services; +using Svg.Skia.TypefaceProviders; namespace Svg.Skia; @@ -98,7 +99,33 @@ public static void Draw(SkiaSharp.SKCanvas skCanvas, string path, SkiaModel skia public SKPicture? Model { get; private set; } - public virtual SkiaSharp.SKPicture? Picture { get; protected set; } + private SkiaSharp.SKPicture? _picture; + public virtual SkiaSharp.SKPicture? Picture + { + get + { + if (_picture is { }) + { + return _picture; + } + + if (Model is null) + { + return null; + } + + lock (Sync) + { + if (_picture is null && Model is { } model) + { + _picture = SkiaModel.ToSKPicture(model); + } + + return _picture; + } + } + set => _picture = value; + } public SkiaSharp.SKPicture? WireframePicture { get; protected set; } @@ -142,6 +169,41 @@ public SKSvg() AssetLoader = new SkiaSvgAssetLoader(SkiaModel); } + /// + /// Creates a deep copy of the current SKSvg instance, replicating its internal state. + /// + /// A new instance of SKSvg with the same settings, parameters, and model as the original object. + /// The clone will include the configuration of Settings, IgnoreAttributes, Wireframe, Parameters, + /// and a deep copy of the Model if it exists. + public SKSvg Clone() + { + lock (Sync) + { + var clone = new SKSvg + { + _ignoreAttributes = _ignoreAttributes, + _wireframe = _wireframe, + _originalParameters = _originalParameters + }; + + clone.Settings.AlphaType = Settings.AlphaType; + clone.Settings.ColorType = Settings.ColorType; + clone.Settings.Srgb = Settings.Srgb; + clone.Settings.SrgbLinear = Settings.SrgbLinear; + clone.Settings.TypefaceProviders = Settings.TypefaceProviders is null + ? null + : new List(Settings.TypefaceProviders); + + if (Model is { } model) + { + var modelClone = model.Clone(); + clone.Model = modelClone; + } + + return clone; + } + } + public SkiaSharp.SKPicture? Load(System.IO.Stream stream, SvgParameters? parameters = null) { SvgDocument? svgDocument; @@ -328,8 +390,8 @@ private void Reset() Model = null; Drawable = null; - Picture?.Dispose(); - Picture = null; + _picture?.Dispose(); + _picture = null; WireframePicture?.Dispose(); WireframePicture = null;