diff --git a/README.md b/README.md index 363103d3b2..92180ca76d 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,42 @@ using (var svg = new SKSvg()) } ``` +### Hit Testing + +#### SKSvg + +The `SKSvg` class provides helpers for retrieving elements or drawables at a +given point. The hit-testing methods expect coordinates in picture space: + +```C# +using SkiaSharp; +using Svg.Skia; + +var svg = new SKSvg(); +if (svg.Load("image.svg") is { }) +{ + var element = svg.HitTestElements(new SKPoint(10, 10)).FirstOrDefault(); + if (element is { }) + { + Console.WriteLine(element.ID); + } +} +``` + +When drawing on a transformed canvas you can convert canvas coordinates to +picture coordinates using `TryGetPicturePoint` and then use the hit-testing +methods. + +#### Svg control + +The `Svg` Avalonia control exposes a `HitTestElements` method that accepts +a point in control coordinates and returns the matching SVG elements: + +```C# +var hits = svgControl.HitTestElements(new Point(x, y)); +``` + + ### Avalonia #### Install Package diff --git a/Svg.Skia.sln b/Svg.Skia.sln index 480320cdc6..df280ef09d 100644 --- a/Svg.Skia.sln +++ b/Svg.Skia.sln @@ -66,6 +66,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7863AE7D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Skia.UnitTests", "tests\Svg.Skia.UnitTests\Svg.Skia.UnitTests.csproj", "{1BD5FA09-D543-4315-99A6-81E9DD8746EC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShimSkiaSharp.UnitTests", "tests\\ShimSkiaSharp.UnitTests\\ShimSkiaSharp.UnitTests.csproj", "{A2367B2B-5371-4ED0-BB9D-9532046F44E8}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{B65D5B3A-77BE-4AFF-B502-A136B9C932F8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Controls.Skia.Avalonia", "src\Svg.Controls.Skia.Avalonia\Svg.Controls.Skia.Avalonia.csproj", "{8BAAB509-6073-4D68-9F16-EA28986839B1}" @@ -113,6 +115,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvaloniaSvgSkiaStylingSampl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Svg.Generators", "externals\Svg.Generators\Svg.Generators.csproj", "{A27DF58D-4865-4233-9D1A-476DF155EC8C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SvgToPng", "samples\SvgToPng\SvgToPng.csproj", "{73BC0285-F170-49CB-ACDF-B5AFCC8F435E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -131,6 +135,10 @@ Global {1BD5FA09-D543-4315-99A6-81E9DD8746EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {1BD5FA09-D543-4315-99A6-81E9DD8746EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {1BD5FA09-D543-4315-99A6-81E9DD8746EC}.Release|Any CPU.Build.0 = Release|Any CPU + {A2367B2B-5371-4ED0-BB9D-9532046F44E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2367B2B-5371-4ED0-BB9D-9532046F44E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2367B2B-5371-4ED0-BB9D-9532046F44E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2367B2B-5371-4ED0-BB9D-9532046F44E8}.Release|Any CPU.Build.0 = Release|Any CPU {8BAAB509-6073-4D68-9F16-EA28986839B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8BAAB509-6073-4D68-9F16-EA28986839B1}.Debug|Any CPU.Build.0 = Debug|Any CPU {8BAAB509-6073-4D68-9F16-EA28986839B1}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -207,6 +215,10 @@ Global {A27DF58D-4865-4233-9D1A-476DF155EC8C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A27DF58D-4865-4233-9D1A-476DF155EC8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A27DF58D-4865-4233-9D1A-476DF155EC8C}.Release|Any CPU.Build.0 = Release|Any CPU + {73BC0285-F170-49CB-ACDF-B5AFCC8F435E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73BC0285-F170-49CB-ACDF-B5AFCC8F435E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73BC0285-F170-49CB-ACDF-B5AFCC8F435E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73BC0285-F170-49CB-ACDF-B5AFCC8F435E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -218,6 +230,7 @@ Global {D0720395-9892-4CE1-9F25-C6117085B6F8} = {32B4A27D-6FC0-498C-9AD8-5510ACF2C4A1} {5BFEF4F8-2BA7-4050-8E5B-F03A072C2B17} = {32B4A27D-6FC0-498C-9AD8-5510ACF2C4A1} {1BD5FA09-D543-4315-99A6-81E9DD8746EC} = {7863AE7D-FF68-45BF-BA68-6FA0E5604CB7} + {A2367B2B-5371-4ED0-BB9D-9532046F44E8} = {7863AE7D-FF68-45BF-BA68-6FA0E5604CB7} {8BAAB509-6073-4D68-9F16-EA28986839B1} = {4C42912C-9F8C-43D9-A4B5-4427F7EC8F18} {81724F00-B7C3-4E25-B473-C7433BABDC81} = {B65D5B3A-77BE-4AFF-B502-A136B9C932F8} {CFA46E73-0050-4C57-85CE-6C5868A2483C} = {C5FFCF4B-86DC-453E-8006-44EE9EEFEE39} @@ -238,6 +251,7 @@ Global {B742F260-0EC6-4805-AE9F-987818CE3CF4} = {4C42912C-9F8C-43D9-A4B5-4427F7EC8F18} {8A938DC2-1634-4387-BAB3-69F871D54FB5} = {B65D5B3A-77BE-4AFF-B502-A136B9C932F8} {A27DF58D-4865-4233-9D1A-476DF155EC8C} = {C5FFCF4B-86DC-453E-8006-44EE9EEFEE39} + {73BC0285-F170-49CB-ACDF-B5AFCC8F435E} = {B65D5B3A-77BE-4AFF-B502-A136B9C932F8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {12D5E557-A27B-4FB2-83A3-4AC75B04B22C} diff --git a/global.json b/global.json index 6f4a523390..73745b92e8 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { - "sdk": { - "version": "9.0.100", - "rollForward": "latestMinor", - "allowPrerelease": true - } + "sdk": { + "version": "9.0.100", + "rollForward": "latestMinor", + "allowPrerelease": true + } } diff --git a/samples/AvaloniaControlsSample/AvaloniaControlsSample.csproj b/samples/AvaloniaControlsSample/AvaloniaControlsSample.csproj index 448d05d556..5c3f01b448 100644 --- a/samples/AvaloniaControlsSample/AvaloniaControlsSample.csproj +++ b/samples/AvaloniaControlsSample/AvaloniaControlsSample.csproj @@ -2,7 +2,7 @@ WinExe - net8.0 + net9.0 latest False disable diff --git a/samples/AvaloniaSKPictureImageSample/AvaloniaSKPictureImageSample.csproj b/samples/AvaloniaSKPictureImageSample/AvaloniaSKPictureImageSample.csproj index e33e9d2f74..a414793b74 100644 --- a/samples/AvaloniaSKPictureImageSample/AvaloniaSKPictureImageSample.csproj +++ b/samples/AvaloniaSKPictureImageSample/AvaloniaSKPictureImageSample.csproj @@ -2,7 +2,7 @@ WinExe - net8.0 + net9.0 False enable latest diff --git a/samples/AvaloniaSvgSample/AvaloniaSvgSample.csproj b/samples/AvaloniaSvgSample/AvaloniaSvgSample.csproj index aed9fe350a..f6f07bf977 100644 --- a/samples/AvaloniaSvgSample/AvaloniaSvgSample.csproj +++ b/samples/AvaloniaSvgSample/AvaloniaSvgSample.csproj @@ -2,7 +2,7 @@ WinExe - net8.0 + net9.0 latest False disable diff --git a/samples/AvaloniaSvgSkiaSample/AvaloniaSvgSkiaSample.csproj b/samples/AvaloniaSvgSkiaSample/AvaloniaSvgSkiaSample.csproj index 5c926949a9..c4a6098dc8 100644 --- a/samples/AvaloniaSvgSkiaSample/AvaloniaSvgSkiaSample.csproj +++ b/samples/AvaloniaSvgSkiaSample/AvaloniaSvgSkiaSample.csproj @@ -2,7 +2,7 @@ WinExe - net8.0 + net9.0 latest False disable diff --git a/samples/AvaloniaSvgSkiaStylingSample/AvaloniaSvgSkiaStylingSample.csproj b/samples/AvaloniaSvgSkiaStylingSample/AvaloniaSvgSkiaStylingSample.csproj index 7c0df47641..de27da77de 100644 --- a/samples/AvaloniaSvgSkiaStylingSample/AvaloniaSvgSkiaStylingSample.csproj +++ b/samples/AvaloniaSvgSkiaStylingSample/AvaloniaSvgSkiaStylingSample.csproj @@ -2,7 +2,7 @@ WinExe - net8.0 + net9.0 latest False disable diff --git a/samples/Svg.Skia.Converter/Svg.Skia.Converter.csproj b/samples/Svg.Skia.Converter/Svg.Skia.Converter.csproj index e50f44466c..d256e6ce5a 100644 --- a/samples/Svg.Skia.Converter/Svg.Skia.Converter.csproj +++ b/samples/Svg.Skia.Converter/Svg.Skia.Converter.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 False False CS1591 diff --git a/samples/Svg.SourceGenerator.Skia.Sample/Svg.SourceGenerator.Skia.Sample.csproj b/samples/Svg.SourceGenerator.Skia.Sample/Svg.SourceGenerator.Skia.Sample.csproj index 8586878ad5..2be5311a0a 100644 --- a/samples/Svg.SourceGenerator.Skia.Sample/Svg.SourceGenerator.Skia.Sample.csproj +++ b/samples/Svg.SourceGenerator.Skia.Sample/Svg.SourceGenerator.Skia.Sample.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 False latest true diff --git a/samples/SvgToPng/MainWindow.xaml.cs b/samples/SvgToPng/MainWindow.xaml.cs index d6e4b16cad..820a403f94 100644 --- a/samples/SvgToPng/MainWindow.xaml.cs +++ b/samples/SvgToPng/MainWindow.xaml.cs @@ -11,6 +11,7 @@ using System.Windows.Controls; using System.Windows.Input; using Svg.Model; +using Svg.Model.Services; using SvgToPng.ViewModels; namespace SvgToPng; @@ -23,7 +24,7 @@ public MainWindow() { InitializeComponent(); #if DEBUG - SvgExtensions.s_systemLanguageOverride = CultureInfo.CreateSpecificCulture("en-US"); + SvgService.s_systemLanguageOverride = CultureInfo.CreateSpecificCulture("en-US"); #endif var vm = MainWindowViewModel.Load("VM.json"); if (vm is { }) @@ -553,4 +554,4 @@ private void OnPaintSurfaceDiff(SkiaSharp.SKCanvas canvas, int width, int height } } } -} \ No newline at end of file +} diff --git a/samples/SvgToPng/SvgToPng.csproj b/samples/SvgToPng/SvgToPng.csproj index 12e5172cd6..14fa9406fb 100644 --- a/samples/SvgToPng/SvgToPng.csproj +++ b/samples/SvgToPng/SvgToPng.csproj @@ -2,12 +2,13 @@ WinExe - net7.0-windows + net9.0-windows true true True disable true + true diff --git a/samples/SvgToPng/ViewModels/MainWindowViewModel.cs b/samples/SvgToPng/ViewModels/MainWindowViewModel.cs index aa7a2a89e4..9f81a4e44a 100644 --- a/samples/SvgToPng/ViewModels/MainWindowViewModel.cs +++ b/samples/SvgToPng/ViewModels/MainWindowViewModel.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using Svg.CodeGen.Skia; using Svg.Model; +using Svg.Model.Services; using Svg.Skia; namespace SvgToPng.ViewModels; @@ -18,7 +19,7 @@ public class MainWindowViewModel { private readonly SKSvgSettings _settings; private readonly SkiaModel _skiaModel; - private readonly IAssetLoader _assetLoader; + private readonly ISvgAssetLoader _assetLoader; [DataMember] public ObservableCollection Items { get; set; } @@ -51,7 +52,7 @@ public MainWindowViewModel() { _settings = new SKSvgSettings(); _skiaModel = new SkiaModel(_settings); - _assetLoader = new SkiaAssetLoader(_skiaModel); + _assetLoader = new SkiaSvgAssetLoader(_skiaModel); } public void CreateItemsView() @@ -115,7 +116,7 @@ private void LoadSvg(Item item, Action statusOpen, Action status } var stopwatchOpen = Stopwatch.StartNew(); - item.Document = SvgExtensions.Open(item.SvgPath); + item.Document = SvgService.Open(item.SvgPath); stopwatchOpen.Stop(); statusOpen?.Invoke($"{Math.Round(stopwatchOpen.Elapsed.TotalMilliseconds, 3)}ms"); Debug.WriteLine($"Open: {Math.Round(stopwatchOpen.Elapsed.TotalMilliseconds, 3)}ms"); @@ -125,7 +126,7 @@ private void LoadSvg(Item item, Action statusOpen, Action status var stopwatchToPicture = Stopwatch.StartNew(); var references = new HashSet {item.Document.BaseUri}; - item.Drawable = SvgExtensions.ToDrawable(item.Document, _assetLoader, references, out var bounds); + item.Drawable = SvgService.ToDrawable(item.Document, _assetLoader, references, out var bounds); if (item.Drawable is { } && bounds is { }) { item.Picture = item.Drawable.Snapshot(bounds.Value); @@ -257,7 +258,7 @@ public void ExportItem(string svgPath, string outputPath, SkiaSharp.SKColor back if (string.Compare(extension, ".pdf", StringComparison.OrdinalIgnoreCase) == 0) { - var svg = SvgExtensions.Open(svgPath); + var svg = SvgService.Open(svgPath); if (svg is { }) { using var picture = SKSvg.ToPicture(svg, _skiaModel, _assetLoader); @@ -269,7 +270,7 @@ public void ExportItem(string svgPath, string outputPath, SkiaSharp.SKColor back } else if (string.Compare(extension, ".xps", StringComparison.OrdinalIgnoreCase) == 0) { - var svg = SvgExtensions.Open(svgPath); + var svg = SvgService.Open(svgPath); if (svg is { }) { using var picture = SKSvg.ToPicture(svg, _skiaModel, _assetLoader); @@ -281,7 +282,7 @@ public void ExportItem(string svgPath, string outputPath, SkiaSharp.SKColor back } else if (string.Compare(extension, ".svg", StringComparison.OrdinalIgnoreCase) == 0) { - var svg = SvgExtensions.Open(svgPath); + var svg = SvgService.Open(svgPath); if (svg is { }) { using var picture = SKSvg.ToPicture(svg, _skiaModel, _assetLoader); @@ -293,7 +294,7 @@ public void ExportItem(string svgPath, string outputPath, SkiaSharp.SKColor back } else if (string.Compare(extension, ".jpeg", StringComparison.OrdinalIgnoreCase) == 0) { - var svg = SvgExtensions.Open(svgPath); + var svg = SvgService.Open(svgPath); if (svg is { }) { using var picture = SKSvg.ToPicture(svg, _skiaModel, _assetLoader); @@ -307,7 +308,7 @@ public void ExportItem(string svgPath, string outputPath, SkiaSharp.SKColor back } else if (string.Compare(extension, ".jpg", StringComparison.OrdinalIgnoreCase) == 0) { - var svg = SvgExtensions.Open(svgPath); + var svg = SvgService.Open(svgPath); if (svg is { }) { using var picture = SKSvg.ToPicture(svg, _skiaModel, _assetLoader); @@ -321,7 +322,7 @@ public void ExportItem(string svgPath, string outputPath, SkiaSharp.SKColor back } else if (string.Compare(extension, ".png", StringComparison.OrdinalIgnoreCase) == 0) { - var svg = SvgExtensions.Open(svgPath); + var svg = SvgService.Open(svgPath); if (svg is { }) { using var picture = SKSvg.ToPicture(svg, _skiaModel, _assetLoader); @@ -335,7 +336,7 @@ public void ExportItem(string svgPath, string outputPath, SkiaSharp.SKColor back } else if (string.Compare(extension, ".webp", StringComparison.OrdinalIgnoreCase) == 0) { - var svg = SvgExtensions.Open(svgPath); + var svg = SvgService.Open(svgPath); if (svg is { }) { using var picture = SKSvg.ToPicture(svg, _skiaModel, _assetLoader); diff --git a/samples/TestApp/Views/MainView.axaml b/samples/TestApp/Views/MainView.axaml index 30616c9c05..e43c992f84 100644 --- a/samples/TestApp/Views/MainView.axaml +++ b/samples/TestApp/Views/MainView.axaml @@ -12,7 +12,7 @@ - + + SelectedItem="{Binding SelectedItem, Mode=TwoWay}" + SelectionChanged="SelectingItemsControl_OnSelectionChanged">