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;