├── .gitignore
├── BuildUtilites
└── VersionIncrement.ps1
├── LICENSE
├── README.md
├── TestApp
├── App.config
├── App.xaml
├── App.xaml.cs
├── MainViewModel.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── TestApp.csproj
└── packages.config
├── UnitTests
└── VectorGraphicsHelperTests
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── VectorGeometryHelperTests.cs
│ ├── VectorGraphicParserTests.cs
│ ├── VectorGraphicsHelperTests.csproj
│ └── packages.config
├── VectorGraphicsHelper
├── Enums.cs
├── Extensions.cs
├── Properties
│ └── AssemblyInfo.cs
├── VectorCommand.cs
├── VectorGeometryHelper.cs
├── VectorGraphicParser.cs
├── VectorGraphicsHelper.csproj
└── packages.config
├── WpfDirect2d.sln
└── WpfDirect2d
├── Direct2dSurface.xaml
├── Direct2dSurface.xaml.cs
├── Enums.cs
├── Extensions.cs
├── FodyWeavers.xml
├── Properties
└── AssemblyInfo.cs
├── Shapes
├── BaseGeometry.cs
├── GeometryPath.cs
├── IShape.cs
├── LineGeometry.cs
├── LineShape.cs
└── VectorShape.cs
├── WpfDirect2D.nuspec
├── WpfDirect2d.csproj
└── packages.config
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | [Xx]64/
19 | [Xx]86/
20 | [Bb]uild/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 |
85 | # Visual Studio profiler
86 | *.psess
87 | *.vsp
88 | *.vspx
89 | *.sap
90 |
91 | # TFS 2012 Local Workspace
92 | $tf/
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 | *.DotSettings.user
101 |
102 | # JustCode is a .NET coding add-in
103 | .JustCode
104 |
105 | # TeamCity is a build add-in
106 | _TeamCity*
107 |
108 | # DotCover is a Code Coverage Tool
109 | *.dotCover
110 |
111 | # NCrunch
112 | _NCrunch_*
113 | .*crunch*.local.xml
114 | nCrunchTemp_*
115 |
116 | # MightyMoose
117 | *.mm.*
118 | AutoTest.Net/
119 |
120 | # Web workbench (sass)
121 | .sass-cache/
122 |
123 | # Installshield output folder
124 | [Ee]xpress/
125 |
126 | # DocProject is a documentation generator add-in
127 | DocProject/buildhelp/
128 | DocProject/Help/*.HxT
129 | DocProject/Help/*.HxC
130 | DocProject/Help/*.hhc
131 | DocProject/Help/*.hhk
132 | DocProject/Help/*.hhp
133 | DocProject/Help/Html2
134 | DocProject/Help/html
135 |
136 | # Click-Once directory
137 | publish/
138 |
139 | # Publish Web Output
140 | *.[Pp]ublish.xml
141 | *.azurePubxml
142 |
143 | # TODO: Un-comment the next line if you do not want to checkin
144 | # your web deploy settings because they may include unencrypted
145 | # passwords
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # NuGet Packages
150 | *.nupkg
151 | # The packages folder can be ignored because of Package Restore
152 | **/packages/*
153 | # except build/, which is used as an MSBuild target.
154 | !**/packages/build/
155 | # Uncomment if necessary however generally it will be regenerated when needed
156 | #!**/packages/repositories.config
157 | # NuGet v3's project.json files produces more ignoreable files
158 | *.nuget.props
159 | *.nuget.targets
160 |
161 | # Microsoft Azure Build Output
162 | csx/
163 | *.build.csdef
164 |
165 | # Microsoft Azure Emulator
166 | ecf/
167 | rcf/
168 |
169 | # Windows Store app package directory
170 | AppPackages/
171 | BundleArtifacts/
172 |
173 | # Visual Studio cache files
174 | # files ending in .cache can be ignored
175 | *.[Cc]ache
176 | # but keep track of directories ending in .cache
177 | !*.[Cc]ache/
178 |
179 | # Others
180 | ClientBin/
181 | [Ss]tyle[Cc]op.*
182 | ~$*
183 | *~
184 | *.dbmdl
185 | *.dbproj.schemaview
186 | *.pfx
187 | *.publishsettings
188 | node_modules/
189 | orleans.codegen.cs
190 |
191 | # RIA/Silverlight projects
192 | Generated_Code/
193 |
194 | # Backup & report files from converting an old project file
195 | # to a newer Visual Studio version. Backup files are not needed,
196 | # because we have git ;-)
197 | _UpgradeReport_Files/
198 | Backup*/
199 | UpgradeLog*.XML
200 | UpgradeLog*.htm
201 |
202 | # SQL Server files
203 | *.mdf
204 | *.ldf
205 |
206 | # Business Intelligence projects
207 | *.rdl.data
208 | *.bim.layout
209 | *.bim_*.settings
210 |
211 | # Microsoft Fakes
212 | FakesAssemblies/
213 |
214 | # GhostDoc plugin setting file
215 | *.GhostDoc.xml
216 |
217 | # Node.js Tools for Visual Studio
218 | .ntvs_analysis.dat
219 |
220 | # Visual Studio 6 build log
221 | *.plg
222 |
223 | # Visual Studio 6 workspace options file
224 | *.opt
225 |
226 | # Visual Studio LightSwitch build output
227 | **/*.HTMLClient/GeneratedArtifacts
228 | **/*.DesktopClient/GeneratedArtifacts
229 | **/*.DesktopClient/ModelManifest.xml
230 | **/*.Server/GeneratedArtifacts
231 | **/*.Server/ModelManifest.xml
232 | _Pvt_Extensions
233 |
234 | # LightSwitch generated files
235 | GeneratedArtifacts/
236 | ModelManifest.xml
237 |
238 | # Paket dependency manager
239 | .paket/paket.exe
240 |
241 | # FAKE - F# Make
242 | .fake/
243 |
244 | /.axoCover/
245 |
--------------------------------------------------------------------------------
/BuildUtilites/VersionIncrement.ps1:
--------------------------------------------------------------------------------
1 | ##-----------------------------------------------------------------------
2 | ## (c) Microsoft Corporation. This source is subject to the Microsoft Permissive License. See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx. All other rights reserved.
3 | ##-----------------------------------------------------------------------
4 | # Look for a 0.0.0.0 pattern in the build number.
5 | # If found use it to version the assemblies.
6 | #
7 | # For example, if the 'Build number format' build process parameter
8 | # $(BuildDefinitionName)_$(Year:yyyy).$(Month).$(DayOfMonth)$(Rev:.r)
9 | # then your build numbers come out like this:
10 | # "Build HelloWorld_2013.07.19.1"
11 | # This script would then apply version 2013.07.19.1 to your assemblies.
12 |
13 | # Enable -Verbose option
14 | [CmdletBinding()]
15 |
16 | # Regular expression pattern to find the version in the build number
17 | # and then apply it to the assemblies
18 | $VersionRegex = "\d+\.\d+\.\d+\.\d+"
19 |
20 | # If this script is not running on a build server, remind user to
21 | # set environment variables so that this script can be debugged
22 | if(-not ($Env:BUILD_SOURCESDIRECTORY -and $Env:BUILD_BUILDNUMBER))
23 | {
24 | Write-Error "You must set the following environment variables"
25 | Write-Error "to test this script interactively."
26 | Write-Host '$Env:BUILD_SOURCESDIRECTORY - For example, enter something like:'
27 | Write-Host '$Env:BUILD_SOURCESDIRECTORY = "C:\code\FabrikamTFVC\HelloWorld"'
28 | Write-Host '$Env:BUILD_BUILDNUMBER - For example, enter something like:'
29 | Write-Host '$Env:BUILD_BUILDNUMBER = "Build HelloWorld_0000.00.00.0"'
30 | exit 1
31 | }
32 |
33 | # Make sure path to source code directory is available
34 | if (-not $Env:BUILD_SOURCESDIRECTORY)
35 | {
36 | Write-Error ("BUILD_SOURCESDIRECTORY environment variable is missing.")
37 | exit 1
38 | }
39 | elseif (-not (Test-Path $Env:BUILD_SOURCESDIRECTORY))
40 | {
41 | Write-Error "BUILD_SOURCESDIRECTORY does not exist: $Env:BUILD_SOURCESDIRECTORY"
42 | exit 1
43 | }
44 | Write-Verbose "BUILD_SOURCESDIRECTORY: $Env:BUILD_SOURCESDIRECTORY"
45 |
46 | # Make sure there is a build number
47 | if (-not $Env:BUILD_BUILDNUMBER)
48 | {
49 | Write-Error ("BUILD_BUILDNUMBER environment variable is missing.")
50 | exit 1
51 | }
52 | Write-Verbose "BUILD_BUILDNUMBER: $Env:BUILD_BUILDNUMBER"
53 |
54 | # Get and validate the version data
55 | $VersionData = [regex]::matches($Env:BUILD_BUILDNUMBER,$VersionRegex)
56 | switch($VersionData.Count)
57 | {
58 | 0
59 | {
60 | Write-Error "Could not find version number data in BUILD_BUILDNUMBER."
61 | exit 1
62 | }
63 | 1 {}
64 | default
65 | {
66 | Write-Warning "Found more than instance of version data in BUILD_BUILDNUMBER."
67 | Write-Warning "Will assume first instance is version."
68 | }
69 | }
70 | $NewVersion = $VersionData[0]
71 | Write-Verbose "Version: $NewVersion"
72 |
73 | # Apply the version to the assembly property files
74 | $files = gci $Env:BUILD_SOURCESDIRECTORY -recurse -include "*Properties*","My Project" |
75 | ?{ $_.PSIsContainer } |
76 | foreach { gci -Path $_.FullName -Recurse -include AssemblyInfo.* }
77 | if($files)
78 | {
79 | Write-Verbose "Will apply $NewVersion to $($files.count) files."
80 |
81 | foreach ($file in $files) {
82 | $filecontent = Get-Content($file)
83 | attrib $file -r
84 | $filecontent -replace $VersionRegex, $NewVersion | Out-File $file
85 | Write-Verbose "$file.FullName - version applied"
86 | }
87 | }
88 | else
89 | {
90 | Write-Warning "Found no files."
91 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 ljchristinson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WPFDirect2D
2 | WPF Control that supports rendering geometries / shapes via Direct2D.
3 |
4 | 
5 | [](https://www.nuget.org/packages/WPF.Direct2D.Surface/)
6 |
7 |
8 | ## Documentation
9 | Documentation can be found on the [Wiki](https://github.com/ljchristinson/WPFDirect2D/wiki)
10 |
--------------------------------------------------------------------------------
/TestApp/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TestApp/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/TestApp/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace TestApp
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/TestApp/MainViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using System.Windows.Input;
6 | using System.Windows.Media;
7 | using Prism.Commands;
8 | using WpfDirect2D.Shapes;
9 | using Prism.Mvvm;
10 | using System.Linq;
11 | using System.Windows;
12 |
13 | namespace TestApp
14 | {
15 | public class MainViewModel : BindableBase
16 | {
17 | private const string DOT_CIRCLE_VECTOR = "F1 M 38,27.1542C 43.99,27.1542 48.8458,32.01 48.8458,38C 48.8458,43.99 43.99,48.8458 38,48.8458C 32.01,48.8458 27.1542,43.99 27.1542,38C 27.1542,32.01 32.01,27.1542 38,27.1542 Z M 38,16.625C 49.8051,16.625 59.375,26.1949 59.375,38C 59.375,49.8051 49.8051,59.375 38,59.375C 26.1949,59.375 16.625,49.8051 16.625,38C 16.625,26.1949 26.1949,16.625 38,16.625 Z M 38,20.5833C 28.381,20.5833 20.5833,28.381 20.5833,38C 20.5833,47.619 28.381,55.4167 38,55.4167C 47.6189,55.4167 55.4167,47.619 55.4167,38C 55.4167,28.381 47.619,20.5833 38,20.5833 Z";
18 | private const string MOVE_VECTOR = "F1 M 34,39.1716L 34,22.4583L 28,28.0833L 28,22.5833L 36,15.0833L 44,22.5833L 44,28.0833L 38,22.4583L 38,38L 54.625,38L 49,32L 54.5,32L 62,40L 54.5,48L 49,48L 54.625,42L 36.8284,42L 25.8284,53L 34,53L 30,57L 19,57L 19,46L 23,42L 23,50.1716L 34,39.1716 Z M 40.2533,47.5333L 37.8,52.345L 37.8,55L 36.0933,55L 36.0933,52.375L 33.7467,47.5333L 35.6683,47.5333L 36.8633,50.3183L 37.0333,50.9283L 37.055,50.9283L 37.22,50.34L 38.4717,47.5333L 40.2533,47.5333 Z M 48.52,37L 46.49,37L 45.1817,34.5683L 45.0283,34.0683L 45.0067,34.0683L 44.8317,34.59L 43.5183,37L 41.48,37L 43.9,33.2667L 41.6933,29.5333L 43.7717,29.5333L 44.855,31.7717L 45.0817,32.4017L 45.1033,32.4017L 45.3383,31.7517L 46.5317,29.5333L 48.4133,29.5333L 46.1783,33.235L 48.52,37 Z M 32.04,38L 25.96,38L 25.96,37.015L 29.835,31.92L 26.28,31.92L 26.28,30.5333L 32.04,30.5333L 32.04,31.4883L 28.2483,36.6133L 32.04,36.6133L 32.04,38 Z";
19 |
20 | private int _numberOfItemsToRender;
21 | private readonly Random _random;
22 | private IShape _selectedShape;
23 | private bool _rerender;
24 |
25 | public MainViewModel()
26 | {
27 | _random = new Random(10);
28 | ApplyNumberOfRenderItems = new DelegateCommand(() => GenerateShapes());
29 | NumberOfItemsToRender = 10;
30 | Geometries = new List();
31 |
32 | Task.Run(() =>
33 | {
34 | while (true)
35 | {
36 | Thread.Sleep(100);
37 | Geometries = new List(Geometries);
38 | RaisePropertyChanged(nameof(Geometries));
39 |
40 | var item = Geometries.ElementAtOrDefault(_random.Next(0, Geometries.Count));
41 | if (item != null)
42 | {
43 | item.IsSelected = !item.IsSelected;
44 | item.FillColor = Colors.Yellow;
45 | }
46 | }
47 | });
48 | }
49 |
50 | public ICommand ApplyNumberOfRenderItems { get; private set; }
51 |
52 | public int NumberOfItemsToRender
53 | {
54 | get { return _numberOfItemsToRender; }
55 | set { SetProperty(ref _numberOfItemsToRender, value); }
56 | }
57 |
58 | public List Geometries { get; private set; }
59 |
60 | public IShape SelectedShape
61 | {
62 | get { return _selectedShape; }
63 | set { _selectedShape = value; }
64 | }
65 |
66 | public bool Rerender
67 | {
68 | get { return _rerender; }
69 | set { SetProperty(ref _rerender, value); }
70 | }
71 |
72 | private void GenerateShapes()
73 | {
74 | Geometries = null;
75 | var geometryList = new List();
76 |
77 | int count = 0;
78 | while (count < NumberOfItemsToRender)
79 | {
80 | var shape = new VectorShape
81 | {
82 | GeometryPath = DOT_CIRCLE_VECTOR,
83 | PixelXLocation = GetRandomPixelLocation(),
84 | PixelYLocation = GetRandomPixelLocation(),
85 | FillColor = Colors.Black,
86 | StrokeColor = Colors.Black,
87 | SelectedColor = Colors.Green,
88 | StrokeWidth = 0.5f,
89 | Scaling = 0.2f
90 | };
91 | shape.BrushColorsToCache.Add(Colors.Yellow);
92 | shape.BrushColorsToCache.Add(Colors.Wheat);
93 | shape.BrushColorsToCache.Add(Colors.Turquoise);
94 | shape.BrushColorsToCache.Add(Colors.SaddleBrown);
95 | geometryList.Add(shape);
96 |
97 | geometryList.Add(new VectorShape
98 | {
99 | GeometryPath = DOT_CIRCLE_VECTOR,
100 | PixelXLocation = GetRandomPixelLocation(),
101 | PixelYLocation = GetRandomPixelLocation(),
102 | FillColor = Colors.Blue,
103 | StrokeColor = Colors.Red,
104 | SelectedColor = Colors.Green,
105 | StrokeWidth = 0.5f,
106 | Scaling = 0.4f
107 | });
108 |
109 | geometryList.Add(new VectorShape
110 | {
111 | GeometryPath = MOVE_VECTOR,
112 | PixelXLocation = GetRandomPixelLocation(),
113 | PixelYLocation = GetRandomPixelLocation(),
114 | FillColor = Colors.Black,
115 | StrokeColor = Colors.Black,
116 | SelectedColor = Colors.Green,
117 | StrokeWidth = 0.5f,
118 | Scaling = 0.6f
119 | });
120 |
121 | geometryList.Add(new VectorShape
122 | {
123 | GeometryPath = MOVE_VECTOR,
124 | PixelXLocation = GetRandomPixelLocation(),
125 | PixelYLocation = GetRandomPixelLocation(),
126 | FillColor = Colors.Blue,
127 | StrokeColor = Colors.Red,
128 | SelectedColor = Colors.Green,
129 | StrokeWidth = 0.5f,
130 | Scaling = 0.8f
131 | });
132 |
133 | count += 4;
134 | }
135 |
136 | //add a line
137 | var line = new LineShape
138 | {
139 | FillColor = Colors.Blue,
140 | StrokeColor = Colors.Blue,
141 | SelectedColor = Colors.PaleVioletRed,
142 | StrokeWidth = 4f,
143 | IsLineClosed = false
144 | };
145 | line.LineNodes.Add(new Point(10, 40));
146 | line.LineNodes.Add(new Point(400, 10));
147 | line.LineNodes.Add(new Point(400, 400));
148 | geometryList.Add(line);
149 |
150 | //int numLines = 4000;
151 | //for (int i = 0; i < numLines; i++)
152 | //{
153 | // var line = new LineShape
154 | // {
155 | // FillColor = Colors.Blue,
156 | // StrokeColor = Colors.Blue,
157 | // SelectedColor = Colors.PaleVioletRed,
158 | // StrokeWidth = 4f,
159 | // IsLineClosed = false
160 | // };
161 | // line.LineNodes.Add(new Point(GetRandomPixelLocation(), GetRandomPixelLocation()));
162 | // line.LineNodes.Add(new Point(GetRandomPixelLocation(), GetRandomPixelLocation()));
163 | // geometryList.Add(line);
164 | //}
165 |
166 |
167 | Geometries = new List(geometryList);
168 | RaisePropertyChanged(nameof(Geometries));
169 | }
170 |
171 | private int GetRandomPixelLocation()
172 | {
173 | return _random.Next(10, 1024);
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/TestApp/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/TestApp/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace TestApp
17 | {
18 | ///
19 | /// Interaction logic for MainWindow.xaml
20 | ///
21 | public partial class MainWindow : Window
22 | {
23 | public MainWindow()
24 | {
25 | InitializeComponent();
26 | DataContext = new MainViewModel();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/TestApp/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("TestApp")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("TestApp")]
15 | [assembly: AssemblyCopyright("Copyright © 2016")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/TestApp/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace TestApp.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TestApp.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/TestApp/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/TestApp/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace TestApp.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/TestApp/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/TestApp/TestApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}
8 | WinExe
9 | Properties
10 | TestApp
11 | TestApp
12 | v4.5
13 | 512
14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 | 4
16 | true
17 |
18 |
19 |
20 | AnyCPU
21 | true
22 | full
23 | false
24 | bin\Debug\
25 | DEBUG;TRACE
26 | prompt
27 | 4
28 | false
29 |
30 |
31 | AnyCPU
32 | pdbonly
33 | true
34 | bin\Release\
35 | TRACE
36 | prompt
37 | 4
38 |
39 |
40 |
41 | ..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll
42 | True
43 |
44 |
45 | ..\packages\Prism.Core.6.3.0\lib\net45\Prism.dll
46 |
47 |
48 | ..\packages\Prism.Wpf.6.3.0\lib\net45\Prism.Wpf.dll
49 |
50 |
51 |
52 |
53 | ..\packages\Prism.Wpf.6.3.0\lib\net45\System.Windows.Interactivity.dll
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 4.0
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | MSBuild:Compile
71 | Designer
72 |
73 |
74 | MSBuild:Compile
75 | Designer
76 |
77 |
78 | App.xaml
79 | Code
80 |
81 |
82 |
83 | MainWindow.xaml
84 | Code
85 |
86 |
87 |
88 |
89 | Code
90 |
91 |
92 | True
93 | True
94 | Resources.resx
95 |
96 |
97 | True
98 | Settings.settings
99 | True
100 |
101 |
102 | ResXFileCodeGenerator
103 | Resources.Designer.cs
104 |
105 |
106 |
107 | SettingsSingleFileGenerator
108 | Settings.Designer.cs
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | {24f3bfba-5305-4773-816a-0acb2b968992}
118 | WpfDirect2D
119 |
120 |
121 |
122 |
129 |
--------------------------------------------------------------------------------
/TestApp/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/UnitTests/VectorGraphicsHelperTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("VectorGraphicsHelperTests")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("VectorGraphicsHelperTests")]
13 | [assembly: AssemblyCopyright("Copyright © 2017")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("07ebd962-ce59-4e72-8bd6-cd4c662a3fba")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/UnitTests/VectorGraphicsHelperTests/VectorGeometryHelperTests.cs:
--------------------------------------------------------------------------------
1 | using NSubstitute;
2 | using NUnit.Framework;
3 | using SharpDX;
4 | using SharpDX.Direct2D1;
5 | using SharpDX.Mathematics.Interop;
6 | using VectorGraphicsHelper;
7 |
8 | namespace VectorGraphicsHelperTests
9 | {
10 | public class VectorGeometryHelperTests
11 | {
12 | private GeometrySink _sink;
13 | private VectorGeometryHelper _sut;
14 |
15 | [SetUp]
16 | public void Init()
17 | {
18 | _sink = Substitute.For();
19 | _sut = new VectorGeometryHelper(_sink);
20 | }
21 |
22 | [Test]
23 | public void MoveFigureClosedTest()
24 | {
25 | string svg = "M 28,27";
26 | var commands = VectorGraphicParser.ParsePathData(svg);
27 | _sut.Execute(commands);
28 |
29 | var startPoint = new RawVector2(28f, 27f);
30 | _sink.Received(1).BeginFigure(startPoint, FigureBegin.Filled);
31 |
32 | Assert.IsTrue(_sut.IsFigureOpen);
33 | }
34 |
35 | [Test]
36 | public void MoveFigureOpenTest()
37 | {
38 | string svg = "M 28,27";
39 | var commands = VectorGraphicParser.ParsePathData(svg);
40 | //this will open the figure
41 | _sut.Execute(commands);
42 | _sink.ClearReceivedCalls();
43 |
44 | //execute it again, and the figure should be closed and then reopened
45 | _sut.Execute(commands);
46 |
47 | var startPoint = new RawVector2(28f, 27f);
48 | _sink.Received(1).BeginFigure(startPoint, FigureBegin.Filled);
49 |
50 | Assert.IsTrue(_sut.IsFigureOpen);
51 | _sink.Received(1).EndFigure(FigureEnd.Open);
52 | }
53 |
54 | [Test]
55 | public void LineTest()
56 | {
57 | string svg = "L 28,27";
58 | var commands = VectorGraphicParser.ParsePathData(svg);
59 | _sut.Execute(commands);
60 |
61 | _sink.Received(1).AddLines(Arg.Is(x => x.Length == 1 && x[0].X == 28 && x[0].Y == 27));
62 | }
63 |
64 | [Test]
65 | public void HorizontalLineTest()
66 | {
67 | string svg = "M 28,27 H 13";
68 | var commands = VectorGraphicParser.ParsePathData(svg);
69 | _sut.Execute(commands);
70 |
71 | //horizontal line uses the y position from the move command
72 | _sink.Received(1).AddLines(Arg.Is(x => x.Length == 1 && x[0].X == 13 && x[0].Y == 27));
73 | }
74 |
75 | [Test]
76 | public void VerticalLineTest()
77 | {
78 | string svg = "M 28,27 V 13";
79 | var commands = VectorGraphicParser.ParsePathData(svg);
80 | _sut.Execute(commands);
81 |
82 | //vertical line uses the x position from the move command
83 | _sink.Received(1).AddLines(Arg.Is(x => x.Length == 1 && x[0].X == 28 && x[0].Y == 13));
84 | }
85 |
86 | [TestCase(true, SweepDirection.Clockwise)]
87 | [TestCase(false, SweepDirection.Clockwise)]
88 | [TestCase(true, SweepDirection.CounterClockwise)]
89 | [TestCase(false, SweepDirection.CounterClockwise)]
90 | public void ArcTest(bool isLargeArc, SweepDirection sweepDirection)
91 | {
92 | float rotation = 25;
93 | float width = 10;
94 | float height = 10;
95 | float x = 100;
96 | float y = 200;
97 |
98 | string svg = $"A {width} {height} {rotation} " + (isLargeArc ? " 1" : " 0")
99 | + (sweepDirection == SweepDirection.Clockwise ? " 1" : " 0")
100 | + " " + x + " " + y;
101 | var commands = VectorGraphicParser.ParsePathData(svg);
102 | _sut.Execute(commands);
103 |
104 | var arcSegment = new ArcSegment
105 | {
106 | ArcSize = isLargeArc ? ArcSize.Large : ArcSize.Small,
107 | RotationAngle = rotation,
108 | SweepDirection = sweepDirection,
109 | Point = new Vector2(x, y),
110 | Size = new Size2F(width, height)
111 | };
112 | _sink.Received(2).AddArc(arcSegment);
113 | }
114 |
115 | [Test]
116 | public void CubicBezierCurveTest()
117 | {
118 | float x1 = 10;
119 | float y1 = 20;
120 | float x2 = 30;
121 | float y2 = 35;
122 | float x3 = 0;
123 | float y3 = 5;
124 |
125 | string svg = $"C {x1} {y1} {x2} {y2} {x3} {y3}";
126 | var commands = VectorGraphicParser.ParsePathData(svg);
127 | _sut.Execute(commands);
128 |
129 | var bezSegment = new BezierSegment()
130 | {
131 | Point1 = new Vector2(x1, y1),
132 | Point2 = new Vector2(x2, y2),
133 | Point3 = new Vector2(x3, y3),
134 | };
135 | _sink.Received(1).AddBezier(bezSegment);
136 | }
137 |
138 | [Test]
139 | public void CloseTest()
140 | {
141 | string svg = "Z";
142 | var commands = VectorGraphicParser.ParsePathData(svg);
143 | _sut.Execute(commands);
144 |
145 | _sink.Received(1).EndFigure(FigureEnd.Closed);
146 | Assert.IsFalse(_sut.IsFigureOpen);
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/UnitTests/VectorGraphicsHelperTests/VectorGraphicParserTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System;
3 | using System.Linq;
4 | using VectorGraphicsHelper;
5 |
6 | namespace VectorGraphicsHelperTests
7 | {
8 | public class VectorGraphicParserTests
9 | {
10 | [TestCase("F1 F23 F45", CommandType.FillRule, 3)]
11 | [TestCase("C1 C23", CommandType.CubicBezierCurve, 2)]
12 | [TestCase("A1 a7 a8 A9", CommandType.EllipticalArc, 4)]
13 | [TestCase("H1", CommandType.HorizontalLine, 1)]
14 | [TestCase("L45 L6", CommandType.Line, 2)]
15 | [TestCase("M2 M34.5 M1.5", CommandType.Move, 3)]
16 | [TestCase("Q4", CommandType.QuadraticBezierCurve, 1)]
17 | [TestCase("S4", CommandType.SmoothCubicBezierCurve, 1)]
18 | [TestCase("T4", CommandType.SmoothQuadraticBezierCurve, 1)]
19 | [TestCase("V4 V 5 V7", CommandType.VerticalLine, 3)]
20 | [TestCase("Z1 Z2", CommandType.Close, 2)]
21 | public void ParsePathDataRuleTest(string svgCommand, CommandType expectedType, int expectedCount)
22 | {
23 | var commands = VectorGraphicParser.ParsePathData(svgCommand);
24 |
25 | Assert.AreEqual(expectedCount, commands.Count());
26 | foreach(var command in commands)
27 | {
28 | Assert.AreEqual(expectedType, command.Type);
29 | }
30 | }
31 |
32 | [Test]
33 | public void ParseMultiplePathDataTest()
34 | {
35 | string svg = "F1 M 38,27.1542C 48.8458,38C 48.8458C Z M 38C 16.625 Z M 38,20.5833C 38,55.4167C 38A 38,20.5833 Z";
36 | var commands = VectorGraphicParser.ParsePathData(svg).ToList();
37 |
38 | Assert.AreEqual(14, commands.Count());
39 | Assert.AreEqual(CommandType.FillRule, commands[0].Type);
40 | Assert.AreEqual(CommandType.Move, commands[1].Type);
41 | Assert.AreEqual(CommandType.CubicBezierCurve, commands[2].Type);
42 | Assert.AreEqual(CommandType.CubicBezierCurve, commands[3].Type);
43 | Assert.AreEqual(CommandType.CubicBezierCurve, commands[4].Type);
44 | Assert.AreEqual(CommandType.Close, commands[5].Type);
45 | Assert.AreEqual(CommandType.Move, commands[6].Type);
46 | Assert.AreEqual(CommandType.CubicBezierCurve, commands[7].Type);
47 | Assert.AreEqual(CommandType.Close, commands[8].Type);
48 | Assert.AreEqual(CommandType.Move, commands[9].Type);
49 | Assert.AreEqual(CommandType.CubicBezierCurve, commands[10].Type);
50 | Assert.AreEqual(CommandType.CubicBezierCurve, commands[11].Type);
51 | Assert.AreEqual(CommandType.EllipticalArc, commands[12].Type);
52 | Assert.AreEqual(CommandType.Close, commands[13].Type);
53 | }
54 |
55 | [Test]
56 | public void ParsePathDataArgumentsTest()
57 | {
58 | string svg = "F1 M 38,27.1542C";
59 | var commands = VectorGraphicParser.ParsePathData(svg).ToList();
60 |
61 | Assert.AreEqual(3, commands.Count);
62 | var command = commands[1];
63 |
64 | Assert.AreEqual(CommandType.Move, command.Type);
65 | Assert.AreEqual(2, command.Arguments.Count());
66 | Assert.AreEqual(38, command.Arguments.First());
67 | Assert.AreEqual(27.1542, Math.Round(command.Arguments.Last(), 4));
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/UnitTests/VectorGraphicsHelperTests/VectorGraphicsHelperTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}
8 | Library
9 | Properties
10 | VectorGraphicsHelperTests
11 | VectorGraphicsHelperTests
12 | v4.5
13 | 512
14 |
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 | ..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll
36 |
37 |
38 | ..\..\packages\NUnit.3.7.1\lib\net45\nunit.framework.dll
39 |
40 |
41 | ..\..\packages\SharpDX.4.0.1\lib\net45\SharpDX.dll
42 |
43 |
44 | ..\..\packages\SharpDX.Direct2D1.4.0.1\lib\net45\SharpDX.Direct2D1.dll
45 |
46 |
47 | ..\..\packages\SharpDX.DXGI.4.0.1\lib\net45\SharpDX.DXGI.dll
48 |
49 |
50 | ..\..\packages\SharpDX.Mathematics.4.0.1\lib\net45\SharpDX.Mathematics.dll
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | {25b25f53-7409-40df-89ba-ec33d7b00de0}
72 | VectorGraphicsHelper
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/UnitTests/VectorGraphicsHelperTests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/VectorGraphicsHelper/Enums.cs:
--------------------------------------------------------------------------------
1 | namespace VectorGraphicsHelper
2 | {
3 | public enum CommandType
4 | {
5 | FillRule,
6 | Move,
7 | Line,
8 | HorizontalLine,
9 | VerticalLine,
10 | CubicBezierCurve,
11 | QuadraticBezierCurve,
12 | SmoothCubicBezierCurve,
13 | SmoothQuadraticBezierCurve,
14 | EllipticalArc,
15 | Close
16 | }
17 | }
--------------------------------------------------------------------------------
/VectorGraphicsHelper/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using SharpDX;
3 | using SharpDX.Mathematics.Interop;
4 |
5 | namespace VectorGraphicsHelper
6 | {
7 | public static class Extensions
8 | {
9 | public static List ToRawVector2(this List vectors)
10 | {
11 | List rawVectors = new List();
12 | foreach (var vector in vectors)
13 | {
14 | rawVectors.Add(new RawVector2(vector.X, vector.Y));
15 | }
16 |
17 | return rawVectors;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/VectorGraphicsHelper/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("VectorGraphicsHelper")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("VectorGraphicsHelper")]
13 | [assembly: AssemblyCopyright("Copyright © 2016")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("25b25f53-7409-40df-89ba-ec33d7b00de0")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/VectorGraphicsHelper/VectorCommand.cs:
--------------------------------------------------------------------------------
1 | namespace VectorGraphicsHelper
2 | {
3 | public class VectorCommand
4 | {
5 | public VectorCommand(CommandType type, float[] arguments) : this(type, arguments, false) {}
6 |
7 | public VectorCommand(CommandType type, float[] arguments, bool isRelative)
8 | {
9 | Type = type;
10 | Arguments = arguments;
11 | IsRelative = isRelative;
12 | }
13 |
14 | public float[] Arguments { get; private set; }
15 |
16 | public CommandType Type { get; private set; }
17 |
18 | public bool IsRelative { get; private set; }
19 | }
20 | }
--------------------------------------------------------------------------------
/VectorGraphicsHelper/VectorGeometryHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using SharpDX;
3 | using SharpDX.Direct2D1;
4 |
5 | namespace VectorGraphicsHelper
6 | {
7 | public class VectorGeometryHelper
8 | {
9 | private readonly GeometrySink _sink;
10 | private Vector2 _startPoint;
11 | private Vector2 _previousPoint;
12 |
13 | public bool IsFigureOpen { get; private set; }
14 |
15 | public VectorGeometryHelper(GeometrySink sink)
16 | {
17 | _sink = sink;
18 | }
19 |
20 | public void Execute(IEnumerable commands)
21 | {
22 | foreach (var instruction in commands)
23 | {
24 | bool isRelative = instruction.IsRelative;
25 |
26 | switch (instruction.Type)
27 | {
28 | case CommandType.Move:
29 | Move(instruction, isRelative);
30 | break;
31 |
32 | case CommandType.Line:
33 | Line(instruction, isRelative);
34 | break;
35 |
36 | case CommandType.HorizontalLine:
37 | HorizontalLine(instruction, isRelative);
38 | break;
39 |
40 | case CommandType.VerticalLine:
41 | VerticalLine(instruction, isRelative);
42 | break;
43 |
44 | case CommandType.EllipticalArc:
45 | Arc(instruction, isRelative);
46 | break;
47 |
48 | case CommandType.CubicBezierCurve:
49 | CubicBezierCurve(instruction, isRelative);
50 | break;
51 |
52 | case CommandType.Close:
53 | Close(FigureEnd.Closed);
54 | break;
55 |
56 | }
57 | }
58 | }
59 |
60 | private void HorizontalLine(VectorCommand instruction, bool isRelative)
61 | {
62 | var points = new List();
63 | for (var i = 0; i < instruction.Arguments.Length; i++)
64 | {
65 | var point = new Vector2(instruction.Arguments[i], _previousPoint.Y);
66 | if (isRelative)
67 | {
68 | point += new Vector2(_previousPoint.X, 0);
69 | }
70 | points.Add(point);
71 | _previousPoint = points[i];
72 | }
73 | _sink.AddLines(points.ToRawVector2().ToArray());
74 | }
75 |
76 | private void VerticalLine(VectorCommand instruction, bool isRelative)
77 | {
78 | var points = new List();
79 | for (var i = 0; i < instruction.Arguments.Length; i++)
80 | {
81 | var point = new Vector2(_previousPoint.X, instruction.Arguments[i]);
82 | if (isRelative)
83 | point += new Vector2(0, _previousPoint.Y);
84 | points.Add(point);
85 | _previousPoint = points[i];
86 | }
87 | _sink.AddLines(points.ToRawVector2().ToArray());
88 | }
89 |
90 | private void Move(VectorCommand instruction, bool isRelative)
91 | {
92 | if (IsFigureOpen)
93 | {
94 | Close(FigureEnd.Open);
95 | }
96 |
97 | var point = new Vector2(instruction.Arguments[0], instruction.Arguments[1]);
98 | if (isRelative)
99 | {
100 | point += _startPoint;
101 | }
102 | _startPoint = isRelative ? point + _startPoint : point;
103 | _previousPoint = _startPoint;
104 | _sink.BeginFigure(_startPoint, FigureBegin.Filled);
105 | IsFigureOpen = true;
106 | }
107 |
108 | private void Line(VectorCommand instruction, bool isRelative)
109 | {
110 | var points = new List();
111 | for (var i = 0; i < instruction.Arguments.Length; i = i + 2)
112 | {
113 | var point = new Vector2(instruction.Arguments[i], instruction.Arguments[i + 1]);
114 | if (isRelative)
115 | {
116 | point += _previousPoint;
117 | }
118 | points.Add(point);
119 | _previousPoint = points[i];
120 | }
121 | _sink.AddLines(points.ToRawVector2().ToArray());
122 | }
123 |
124 | private void Arc(VectorCommand instruction, bool isRelative)
125 | {
126 | for (int i = 0; i < instruction.Arguments.Length; i = i + 6)
127 | {
128 | float w = instruction.Arguments[0];
129 | float h = instruction.Arguments[1];
130 | float a = instruction.Arguments[2];
131 | bool isLargeArc = (int)instruction.Arguments[3] == 1;
132 | bool sweepDirection = (int)instruction.Arguments[4] == 1;
133 |
134 | var p = new Vector2(instruction.Arguments[5], instruction.Arguments[6]);
135 | if (isRelative)
136 | {
137 | p += _previousPoint;
138 | }
139 |
140 | var arcSegment = new ArcSegment
141 | {
142 | ArcSize = isLargeArc ? ArcSize.Large : ArcSize.Small,
143 | RotationAngle = a,
144 | SweepDirection = sweepDirection ? SweepDirection.Clockwise : SweepDirection.CounterClockwise,
145 | Point = p,
146 | Size = new Size2F(w, h)
147 | };
148 | _sink.AddArc(arcSegment);
149 | }
150 | }
151 |
152 | private void CubicBezierCurve(VectorCommand instruction, bool isRelative)
153 | {
154 | for (int i = 0; i < instruction.Arguments.Length; i = i + 6)
155 | {
156 | var p1 = new Vector2(instruction.Arguments[0], instruction.Arguments[1]);
157 | var p2 = new Vector2(instruction.Arguments[2], instruction.Arguments[3]);
158 | var p3 = new Vector2(instruction.Arguments[4], instruction.Arguments[5]);
159 | if (isRelative)
160 | {
161 | p1 += _previousPoint;
162 | p2 += _previousPoint;
163 | p3 += _previousPoint;
164 | }
165 |
166 | var bezSegment = new BezierSegment()
167 | {
168 | Point1 = p1,
169 | Point2 = p2,
170 | Point3 = p3
171 | };
172 |
173 | _sink.AddBezier(bezSegment);
174 | }
175 | }
176 |
177 | private void Close(FigureEnd endType)
178 | {
179 | IsFigureOpen = false;
180 | _sink.EndFigure(endType);
181 | }
182 |
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/VectorGraphicsHelper/VectorGraphicParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.RegularExpressions;
5 |
6 | namespace VectorGraphicsHelper
7 | {
8 | public static class VectorGraphicParser
9 | {
10 | ///
11 | /// Parses a string representing a Geometry Path object in the Abbreviated Geometry Syntax (svg / xaml path)
12 | ///
13 | ///
14 | public static IEnumerable ParsePathData(string pathString)
15 | {
16 | const string separators = @"(?=[FMLHVCQSTAZmlhvcqstaz])";
17 | var tokens = Regex.Split(pathString, separators).Where(t => !string.IsNullOrEmpty(t));
18 |
19 | var result = tokens.Select(Parse);
20 |
21 | return result;
22 | }
23 |
24 | private static VectorCommand Parse(string pathString)
25 | {
26 | var cmd = pathString.Cast().Take(1).Single();
27 | string remainingargs = pathString.Substring(1);
28 | const string separators = @"[\s,]|(?=(? !string.IsNullOrEmpty(t));
33 |
34 | float[] floatArgs = splitArgs.Select(float.Parse).ToArray();
35 | bool relative;
36 | var primitiveType = Convert(cmd, out relative);
37 | return new VectorCommand(primitiveType, floatArgs, relative);
38 | }
39 |
40 | private static CommandType Convert(char cmd, out bool relative)
41 | {
42 | relative = char.IsLower(cmd);
43 | char invCmd = char.ToLower(cmd);
44 |
45 | switch (invCmd)
46 | {
47 | case 'f':
48 | return CommandType.FillRule;
49 | case 'l':
50 | return CommandType.Line;
51 | case 'h':
52 | return CommandType.HorizontalLine;
53 | case 'a':
54 | return CommandType.EllipticalArc;
55 | case 'm':
56 | return CommandType.Move;
57 | case 'v':
58 | return CommandType.VerticalLine;
59 | case 'c':
60 | return CommandType.CubicBezierCurve;
61 | case 'q':
62 | return CommandType.QuadraticBezierCurve;
63 | case 's':
64 | return CommandType.SmoothCubicBezierCurve;
65 | case 't':
66 | return CommandType.SmoothQuadraticBezierCurve;
67 | case 'z':
68 | return CommandType.Close;
69 | default:
70 | throw new ArgumentOutOfRangeException($"Command '{cmd}' is not valid");
71 |
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/VectorGraphicsHelper/VectorGraphicsHelper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}
8 | Library
9 | Properties
10 | VectorGraphicsHelper
11 | VectorGraphicsHelper
12 | v4.5
13 | 512
14 |
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 | true
35 | bin\x64\Debug\
36 | DEBUG;TRACE
37 | full
38 | AnyCPU
39 | prompt
40 | MinimumRecommendedRules.ruleset
41 |
42 |
43 | bin\x64\Release\
44 | TRACE
45 | true
46 | pdbonly
47 | x64
48 | prompt
49 | MinimumRecommendedRules.ruleset
50 |
51 |
52 |
53 | ..\packages\SharpDX.4.0.1\lib\net45\SharpDX.dll
54 |
55 |
56 | ..\packages\SharpDX.Direct2D1.4.0.1\lib\net45\SharpDX.Direct2D1.dll
57 |
58 |
59 | ..\packages\SharpDX.DXGI.4.0.1\lib\net45\SharpDX.DXGI.dll
60 |
61 |
62 | ..\packages\SharpDX.Mathematics.4.0.1\lib\net45\SharpDX.Mathematics.dll
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
92 |
--------------------------------------------------------------------------------
/VectorGraphicsHelper/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/WpfDirect2d.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VectorGraphicsHelper", "VectorGraphicsHelper\VectorGraphicsHelper.csproj", "{25B25F53-7409-40DF-89BA-EC33D7B00DE0}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "TestApp\TestApp.csproj", "{9BBCAB19-B336-4FB6-81FD-82127CB0EADB}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfDirect2D", "WpfDirect2d\WpfDirect2D.csproj", "{24F3BFBA-5305-4773-816A-0ACB2B968992}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VectorGraphicsHelperTests", "UnitTests\VectorGraphicsHelperTests\VectorGraphicsHelperTests.csproj", "{07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Debug|MixedPlatforms = Debug|MixedPlatforms
18 | Debug|x64 = Debug|x64
19 | Release|Any CPU = Release|Any CPU
20 | Release|MixedPlatforms = Release|MixedPlatforms
21 | Release|x64 = Release|x64
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Debug|MixedPlatforms.ActiveCfg = Debug|Any CPU
27 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Debug|MixedPlatforms.Build.0 = Debug|Any CPU
28 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Debug|x64.ActiveCfg = Debug|x64
29 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Debug|x64.Build.0 = Debug|x64
30 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Release|MixedPlatforms.ActiveCfg = Release|Any CPU
33 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Release|MixedPlatforms.Build.0 = Release|Any CPU
34 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Release|x64.ActiveCfg = Release|x64
35 | {25B25F53-7409-40DF-89BA-EC33D7B00DE0}.Release|x64.Build.0 = Release|x64
36 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Debug|MixedPlatforms.ActiveCfg = Debug|Any CPU
39 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Debug|MixedPlatforms.Build.0 = Debug|Any CPU
40 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Debug|x64.ActiveCfg = Debug|Any CPU
41 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Debug|x64.Build.0 = Debug|Any CPU
42 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Release|MixedPlatforms.ActiveCfg = Release|Any CPU
45 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Release|MixedPlatforms.Build.0 = Release|Any CPU
46 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Release|x64.ActiveCfg = Release|Any CPU
47 | {9BBCAB19-B336-4FB6-81FD-82127CB0EADB}.Release|x64.Build.0 = Release|Any CPU
48 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Debug|MixedPlatforms.ActiveCfg = Debug|Any CPU
51 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Debug|MixedPlatforms.Build.0 = Debug|Any CPU
52 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Debug|x64.ActiveCfg = Debug|x64
53 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Debug|x64.Build.0 = Debug|x64
54 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Release|MixedPlatforms.ActiveCfg = Release|Any CPU
57 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Release|MixedPlatforms.Build.0 = Release|Any CPU
58 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Release|x64.ActiveCfg = Release|x64
59 | {24F3BFBA-5305-4773-816A-0ACB2B968992}.Release|x64.Build.0 = Release|x64
60 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
62 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Debug|MixedPlatforms.ActiveCfg = Debug|Any CPU
63 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Debug|MixedPlatforms.Build.0 = Debug|Any CPU
64 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Debug|x64.ActiveCfg = Debug|Any CPU
65 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Debug|x64.Build.0 = Debug|Any CPU
66 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
67 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Release|Any CPU.Build.0 = Release|Any CPU
68 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Release|MixedPlatforms.ActiveCfg = Release|Any CPU
69 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Release|MixedPlatforms.Build.0 = Release|Any CPU
70 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Release|x64.ActiveCfg = Release|Any CPU
71 | {07EBD962-CE59-4E72-8BD6-CD4C662A3FBA}.Release|x64.Build.0 = Release|Any CPU
72 | EndGlobalSection
73 | GlobalSection(SolutionProperties) = preSolution
74 | HideSolutionNode = FALSE
75 | EndGlobalSection
76 | EndGlobal
77 |
--------------------------------------------------------------------------------
/WpfDirect2d/Direct2dSurface.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/WpfDirect2d/Direct2dSurface.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Input;
7 | using System.Windows.Interop;
8 | using SharpDX;
9 | using VectorGraphicsHelper;
10 | using WpfDirect2D.Shapes;
11 | using Point = System.Windows.Point;
12 | using Wpf = System.Windows.Media;
13 | using SharpDX.Direct2D1;
14 |
15 | namespace WpfDirect2D
16 | {
17 | ///
18 | /// Interaction logic for Direct2dSurface.xaml
19 | ///
20 | public partial class Direct2DSurface : UserControl, IDisposable
21 | {
22 | private const double ZOOM_IN_FACTOR = 1.1;
23 | private const double ZOOM_OUT_FACTOR = 0.9;
24 |
25 | private bool _disposedValue = false; // To detect redundant calls
26 | private bool _isRenderInitialized;
27 | private DeviceContext1 _context;
28 |
29 | private Point _mouseMoveStartPoint;
30 | private bool _isPanning;
31 | private Factory1 _d2dFactory;
32 | private StrokeStyle _lineStrokeStyle;
33 |
34 | private bool _renderRequiresInit;
35 | private readonly Dictionary _brushResources;
36 | private readonly Dictionary _createdGeometries;
37 |
38 | #region Dependency Properties
39 |
40 | public static readonly DependencyProperty ShapesProperty =
41 | DependencyProperty.Register("Shapes", typeof(IEnumerable), typeof(Direct2DSurface), new PropertyMetadata(OnShapesChanged));
42 |
43 | private static void OnShapesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
44 | {
45 | var control = d as Direct2DSurface;
46 | if (control != null && control._isRenderInitialized)
47 | {
48 | control.SyncBrushesWithShapes();
49 | control.SyncGeometriesWithShapes();
50 | }
51 | control?.RequestRender();
52 | }
53 |
54 | public static readonly DependencyProperty AxisTransformProperty =
55 | DependencyProperty.Register("AxisTransform", typeof(Wpf.ScaleTransform), typeof(Direct2DSurface));
56 |
57 | public static readonly DependencyProperty RenderOriginProperty =
58 | DependencyProperty.Register("RenderOrigin", typeof(ShapeRenderOrigin), typeof(Direct2DSurface), new PropertyMetadata(ShapeRenderOrigin.Center));
59 |
60 | public static readonly DependencyProperty SelectedShapeProperty =
61 | DependencyProperty.Register("SelectedShape", typeof(IShape), typeof(Direct2DSurface), new FrameworkPropertyMetadata { BindsTwoWayByDefault = true });
62 |
63 | public static readonly DependencyProperty IsMouseWheelZoomEnabledProperty =
64 | DependencyProperty.Register("IsMouseWheelZoomEnabled", typeof(bool), typeof(Direct2DSurface), new PropertyMetadata(false));
65 |
66 | public static readonly DependencyProperty IsPanningEnabledProperty =
67 | DependencyProperty.Register("IsPanningEnabled", typeof(bool), typeof(Direct2DSurface));
68 |
69 | public static readonly DependencyProperty IsSelectionEnabledProperty =
70 | DependencyProperty.Register("IsSelectionEnabled", typeof(bool), typeof(Direct2DSurface), new PropertyMetadata(true));
71 |
72 | public static readonly DependencyProperty UseRealizationsProperty =
73 | DependencyProperty.Register("UseRealizations", typeof(bool), typeof(Direct2DSurface), new PropertyMetadata(true));
74 |
75 | public IEnumerable Shapes
76 | {
77 | get { return (IEnumerable)GetValue(ShapesProperty); }
78 | set { SetValue(ShapesProperty, value); }
79 | }
80 |
81 | public IShape SelectedShape
82 | {
83 | get { return (IShape)GetValue(SelectedShapeProperty); }
84 | set { SetValue(SelectedShapeProperty, value); }
85 | }
86 |
87 | public bool IsMouseWheelZoomEnabled
88 | {
89 | get { return (bool)GetValue(IsMouseWheelZoomEnabledProperty); }
90 | set { SetValue(IsMouseWheelZoomEnabledProperty, value); }
91 | }
92 |
93 | public bool IsPanningEnabled
94 | {
95 | get { return (bool)GetValue(IsPanningEnabledProperty); }
96 | set { SetValue(IsPanningEnabledProperty, value); }
97 | }
98 |
99 | public bool IsSelectionEnabled
100 | {
101 | get { return (bool)GetValue(IsSelectionEnabledProperty); }
102 | set { SetValue(IsSelectionEnabledProperty, value); }
103 | }
104 |
105 | public Wpf.ScaleTransform AxisTransform
106 | {
107 | get { return (Wpf.ScaleTransform)GetValue(AxisTransformProperty); }
108 | set { SetValue(AxisTransformProperty, value); }
109 | }
110 |
111 | ///
112 | /// What placement to use when rendering Shapes, center or top left corner
113 | ///
114 | public ShapeRenderOrigin RenderOrigin
115 | {
116 | get { return (ShapeRenderOrigin)GetValue(RenderOriginProperty); }
117 | set { SetValue(RenderOriginProperty, value); }
118 | }
119 |
120 | public bool UseRealizations
121 | {
122 | get { return (bool)GetValue(UseRealizationsProperty); }
123 | set { SetValue(UseRealizationsProperty, value); }
124 | }
125 |
126 | #endregion
127 |
128 | public Direct2DSurface()
129 | {
130 | InitializeComponent();
131 | _createdGeometries = new Dictionary();
132 | _brushResources = new Dictionary();
133 |
134 | Loaded += OnLoaded;
135 | SizeChanged += OnSizeChanged;
136 | }
137 |
138 | ///
139 | /// Are Geometry Realizations valid for this OS version
140 | ///
141 | public bool IsRealizationValid { get; private set; }
142 |
143 | ///
144 | /// Are Geometry Realizations enabled and also valid to use
145 | ///
146 | public bool GeometryRealizationsEnabled => UseRealizations && IsRealizationValid;
147 |
148 | ///
149 | /// Request a render of the geometries defined in the Shapes DP.
150 | ///
151 | public void RequestRender()
152 | {
153 | InteropImage.RequestRender();
154 | }
155 |
156 | protected override Wpf.HitTestResult HitTestCore(Wpf.PointHitTestParameters hitTestParameters)
157 | {
158 | return new Wpf.PointHitTestResult(this, hitTestParameters.HitPoint);
159 | }
160 |
161 | private void OnLoaded(object sender, RoutedEventArgs e)
162 | {
163 | if (_isRenderInitialized)
164 | {
165 | return;
166 | }
167 |
168 | var parentWindow = Window.GetWindow(this);
169 | if (parentWindow != null)
170 | {
171 | //the window owner for the D3DImage is the main window handle
172 | InteropImage.WindowOwner = new WindowInteropHelper(parentWindow).Handle;
173 | //callback for when a render is requested
174 | InteropImage.OnRender = Render;
175 |
176 | _isRenderInitialized = true;
177 |
178 | //request one frame to be rendered
179 | InteropImage.RequestRender();
180 |
181 | if (_d2dFactory != null)
182 | {
183 | SyncBrushesWithShapes();
184 | SyncGeometriesWithShapes();
185 | }
186 | }
187 | }
188 |
189 | protected virtual void Dispose(bool disposing)
190 | {
191 | if (_disposedValue) return;
192 |
193 | if (disposing)
194 | {
195 | _context.Dispose();
196 | DisposeDeviceResources();
197 |
198 | _lineStrokeStyle.Dispose();
199 | _isRenderInitialized = false;
200 | }
201 |
202 | _disposedValue = true;
203 | }
204 |
205 | // This code added to correctly implement the disposable pattern.
206 | public void Dispose()
207 | {
208 | // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
209 | Dispose(true);
210 | }
211 |
212 | private void InitializeRenderer(IntPtr handle)
213 | {
214 | bool syncShapes = false;
215 |
216 | //if not null dispose the render target
217 | if (_context != null)
218 | {
219 | _context.Dispose();
220 | DisposeDeviceResources();
221 |
222 | syncShapes = true;
223 | }
224 |
225 | if (InteropImage.PixelHeight == 0 && InteropImage.PixelWidth == 0)
226 | {
227 | SetInteropImagePixelSize();
228 | }
229 |
230 | //create the direct3d 11 device and query interface for DXGI
231 | var comObject = new ComObject(handle);
232 | var resource = comObject.QueryInterface();
233 |
234 | if (_d2dFactory == null)
235 | {
236 | _d2dFactory = new Factory1();
237 | }
238 |
239 | //get a Texture2D resource from Direct3D11 to render to (back buffer)
240 | var texture = resource.QueryInterface();
241 |
242 | //from the texture create a new surface to use as a render target
243 | using (var surface = texture.QueryInterface())
244 | {
245 | var properties = new RenderTargetProperties
246 | {
247 | DpiX = 96,
248 | DpiY = 96,
249 | MinLevel = FeatureLevel.Level_DEFAULT,
250 | PixelFormat = new PixelFormat(SharpDX.DXGI.Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied),
251 | Type = RenderTargetType.Default,
252 | Usage = RenderTargetUsage.None
253 | };
254 |
255 | var renderTarget = new RenderTarget(_d2dFactory, surface, properties);
256 | _context = renderTarget.QueryInterface();
257 | }
258 |
259 | comObject.Dispose();
260 | texture.Dispose();
261 |
262 | _renderRequiresInit = false;
263 |
264 | //check if realizations are allowed (windows 8+)
265 | if (Environment.OSVersion.Version.Major >= 6 && Environment.OSVersion.Version.Minor >= 2)
266 | {
267 | IsRealizationValid = true;
268 | }
269 | else
270 | {
271 | IsRealizationValid = false;
272 | }
273 |
274 | //resync shapes if needed
275 | if (syncShapes)
276 | {
277 | SyncBrushesWithShapes();
278 | SyncGeometriesWithShapes();
279 | }
280 | }
281 |
282 | private void DisposeDeviceResources()
283 | {
284 | foreach (var geometry in _createdGeometries)
285 | {
286 | geometry.Value.Dispose();
287 | }
288 | foreach (var brush in _brushResources)
289 | {
290 | brush.Value.Dispose();
291 | }
292 |
293 | _createdGeometries.Clear();
294 | _brushResources.Clear();
295 | }
296 |
297 | private void OnSizeChanged(object sender, SizeChangedEventArgs e)
298 | {
299 | SetInteropImagePixelSize();
300 |
301 | _renderRequiresInit = true;
302 | InteropImage.RequestRender();
303 | }
304 |
305 | private void SetInteropImagePixelSize()
306 | {
307 | double dpiScale = 1.0; // default value for 96 dpi
308 |
309 | // determine DPI
310 | // (as of .NET 4.6.1, this returns the DPI of the primary monitor, if you have several different DPIs)
311 | PresentationSource presentationSource = PresentationSource.FromVisual(this);
312 | var hwndTarget = presentationSource?.CompositionTarget as HwndTarget;
313 | if (hwndTarget != null)
314 | {
315 | dpiScale = hwndTarget.TransformToDevice.M11;
316 | }
317 |
318 | int surfWidth = (int)(ImageContainer.ActualWidth < 0 ? 0 : Math.Ceiling(ImageContainer.ActualWidth * dpiScale));
319 | int surfHeight = (int)(ImageContainer.ActualHeight < 0 ? 0 : Math.Ceiling(ImageContainer.ActualHeight * dpiScale));
320 |
321 | // notify the D3D11Image and the DxRendering component of the pixel size desired for the DirectX rendering.
322 | InteropImage.SetPixelSize(surfWidth, surfHeight);
323 |
324 | //make sure the resources are created
325 | SyncGeometriesWithShapes();
326 | SyncBrushesWithShapes();
327 | }
328 |
329 | private void Render(IntPtr resourcePointer, bool isNewSurface)
330 | {
331 | if (_context == null || _renderRequiresInit || isNewSurface || _context.IsDisposed)
332 | {
333 | InitializeRenderer(resourcePointer);
334 | }
335 |
336 | if (_context == null || Shapes == null)
337 | {
338 | return;
339 | }
340 |
341 | _context.BeginDraw();
342 | _context.Clear(Color.Transparent);
343 |
344 | //render the geometries
345 | foreach (var shape in Shapes)
346 | {
347 | //get the path geometry for the shape
348 | BaseGeometry pathGeometry;
349 | _createdGeometries.TryGetValue(shape.GeometryHash, out pathGeometry);
350 | if (pathGeometry == null)
351 | {
352 | continue;
353 | }
354 |
355 | //get the fill and stroke brushes
356 | var fillBrush = _brushResources[shape.FillColor];
357 | var strokeBrush = _brushResources[shape.StrokeColor];
358 | var selectedBrush = _brushResources[shape.SelectedColor];
359 |
360 | var vectorShape = shape as VectorShape;
361 | if (vectorShape != null)
362 | {
363 | var transform = pathGeometry.GetRenderTransform(vectorShape.Scaling, vectorShape.PixelXLocation, vectorShape.PixelYLocation, vectorShape.Rotation, RenderOrigin);
364 | if (GeometryRealizationsEnabled)
365 | {
366 | _context.Transform = transform;
367 |
368 | //render the fill realization
369 | _context.DrawGeometryRealization(pathGeometry.FilledRealization, shape.IsSelected ? selectedBrush : fillBrush);
370 |
371 | //render the stroke realization
372 | _context.DrawGeometryRealization(pathGeometry.StrokedRealization, strokeBrush);
373 | }
374 | else
375 | {
376 | var transformedGeometry = new TransformedGeometry(_d2dFactory, pathGeometry.Geometry, transform);
377 |
378 | //render the fill color
379 | _context.FillGeometry(transformedGeometry, shape.IsSelected ? selectedBrush : fillBrush);
380 |
381 | //render the geometry
382 | _context.DrawGeometry(transformedGeometry, strokeBrush, shape.StrokeWidth);
383 | }
384 | }
385 | else
386 | {
387 | //render the line geometry
388 | //lines dont have a set point, it has a series of node points which define the line shape
389 | //translating here isnt needed
390 | _context.Transform = Matrix3x2.Identity;
391 | _context.DrawGeometry(pathGeometry.Geometry, shape.IsSelected ? selectedBrush : strokeBrush, shape.StrokeWidth, _lineStrokeStyle);
392 | }
393 | }
394 | _context.EndDraw();
395 | }
396 |
397 | private void SyncGeometriesWithShapes()
398 | {
399 | if (_context?.Factory == null || Shapes == null)
400 | {
401 | return;
402 | }
403 |
404 | if (_lineStrokeStyle == null)
405 | {
406 | //create the default line stroke style
407 | _lineStrokeStyle = new StrokeStyle(_d2dFactory, new StrokeStyleProperties
408 | {
409 | LineJoin = LineJoin.Round,
410 | StartCap = CapStyle.Round,
411 | EndCap = CapStyle.Round
412 | });
413 | }
414 |
415 | foreach (var shape in Shapes)
416 | {
417 | if (_createdGeometries.ContainsKey(shape.GeometryHash)) continue;
418 |
419 | //vector not created, make it here and store for later
420 | var geometry = CreateGeometry(shape);
421 | if (geometry != null && !_createdGeometries.ContainsKey(geometry.GeometryHash))
422 | {
423 | _createdGeometries.Add(geometry.GeometryHash, geometry);
424 | }
425 | }
426 |
427 | //get list of geometries that are no longer in the Shapes collection, and delete them
428 | foreach (var geoHash in _createdGeometries.Keys.ToList())
429 | {
430 | if (Shapes.All(s => s.GeometryHash != geoHash))
431 | {
432 | _createdGeometries[geoHash].Dispose();
433 | _createdGeometries.Remove(geoHash);
434 | }
435 | }
436 | }
437 |
438 | private BaseGeometry CreateGeometry(IShape shape)
439 | {
440 | if (!shape.IsValid)
441 | {
442 | return null;
443 | }
444 |
445 | var vectorShape = shape as VectorShape;
446 | if (vectorShape != null)
447 | {
448 | var geometry = new PathGeometry(_context.Factory);
449 | var sink = geometry.Open();
450 | VectorGeometryHelper helper = new VectorGeometryHelper(sink);
451 | var commands = VectorGraphicParser.ParsePathData(vectorShape.GeometryPath);
452 | helper.Execute(commands);
453 | sink.Close();
454 |
455 | var shapeGeometry = new GeometryPath(vectorShape.GeometryPath, geometry);
456 | if (GeometryRealizationsEnabled)
457 | {
458 | shapeGeometry.CreateRealizations(_context);
459 | }
460 |
461 | vectorShape.GeometryHash = shapeGeometry.GeometryHash;
462 | return shapeGeometry;
463 | }
464 |
465 | var lineShape = shape as LineShape;
466 | if (lineShape != null)
467 | {
468 | var geometry = new PathGeometry(_context.Factory);
469 | var sink = geometry.Open();
470 |
471 | //first node is the starting point
472 | var startingPoint = lineShape.GetStartingPoint();
473 | sink.BeginFigure(startingPoint.ToRawVector2(), FigureBegin.Filled);
474 | sink.AddLines(lineShape.GetConnectingPoints().ToRawVector2Array());
475 | sink.EndFigure(lineShape.IsLineClosed ? FigureEnd.Closed : FigureEnd.Open);
476 | sink.Close();
477 |
478 | var lineGeometry = new LineGeometry(lineShape.LineNodes, geometry);
479 | lineShape.GeometryHash = lineGeometry.GeometryHash;
480 | return lineGeometry;
481 | }
482 |
483 | return null;
484 | }
485 |
486 | private void SyncBrushesWithShapes()
487 | {
488 | if (_context?.Factory == null || Shapes == null)
489 | {
490 | return;
491 | }
492 |
493 | //add any missing brushes
494 | foreach (var instance in Shapes)
495 | {
496 | foreach (var color in instance.GetColorsToCache())
497 | {
498 | if (!_brushResources.ContainsKey(color))
499 | {
500 | //color missing, add it
501 | var solidBrush = new SolidColorBrush(_context, color.ToDirect2dColor());
502 | _brushResources.Add(color, solidBrush);
503 | }
504 | }
505 | }
506 |
507 | var colorsToDelete = new List();
508 |
509 | //delete any brushes not in use anymore
510 | foreach (var color in _brushResources.Keys)
511 | {
512 | bool colorFound = Shapes.Any(instance => instance.GetColorsToCache().Contains(color));
513 | if (!colorFound)
514 | {
515 | colorsToDelete.Add(color);
516 | }
517 | }
518 |
519 | //remove brushes to be deleted
520 | foreach (var color in colorsToDelete)
521 | {
522 | if (_brushResources.TryGetValue(color, out SolidColorBrush cachedBrush))
523 | {
524 | _brushResources.Remove(color);
525 | cachedBrush.Dispose();
526 | }
527 | }
528 | }
529 |
530 | private void ImageContainer_MouseWheel(object sender, MouseWheelEventArgs e)
531 | {
532 | if (!IsMouseWheelZoomEnabled)
533 | {
534 | return;
535 | }
536 |
537 | double x = e.GetPosition(ImageContainer).X;
538 | double y = e.GetPosition(ImageContainer).Y;
539 | Zoom(e.Delta, new Point(x, y));
540 | }
541 |
542 | private void Zoom(int zoomValue, Point pointToScaleAbout)
543 | {
544 | if (!IsMouseWheelZoomEnabled)
545 | {
546 | return;
547 | }
548 |
549 | Wpf.Matrix imageZoom = ImageContainer.RenderTransform.Value;
550 |
551 | if (zoomValue > 0)
552 | {
553 | imageZoom.ScaleAtPrepend(ZOOM_IN_FACTOR, ZOOM_IN_FACTOR, pointToScaleAbout.X, pointToScaleAbout.Y); // Scale + about current point
554 | }
555 | else
556 | {
557 | imageZoom.ScaleAtPrepend(ZOOM_OUT_FACTOR, ZOOM_OUT_FACTOR, pointToScaleAbout.X, pointToScaleAbout.Y);
558 | if (imageZoom.M22 < 1 || imageZoom.M11 < 1) // If scale value of zoom in either dimension is under 1, reset to identity
559 | {
560 | imageZoom.SetIdentity();
561 | }
562 | }
563 |
564 | ImageContainer.RenderTransform = new Wpf.MatrixTransform(imageZoom);
565 | }
566 |
567 | private void ImageContainer_MouseMove(object sender, MouseEventArgs e)
568 | {
569 | if (!IsPanningEnabled)
570 | {
571 | return;
572 | }
573 |
574 | if (e.LeftButton == MouseButtonState.Pressed)
575 | {
576 | _isPanning = true;
577 |
578 | Wpf.Matrix workPieceImageZoom = ImageContainer.RenderTransform.Value;
579 |
580 | Point currentMousePoint = e.GetPosition(ImageContainer);
581 | Vector dragOffset = currentMousePoint - _mouseMoveStartPoint;
582 |
583 | //smoothing / scaling factor for pan movements
584 | double scaleMultiplier = 5 * Math.Log10(1.0) + 1;
585 | workPieceImageZoom.Translate(dragOffset.X * scaleMultiplier, dragOffset.Y * scaleMultiplier);
586 |
587 | ImageContainer.RenderTransform = new Wpf.MatrixTransform(workPieceImageZoom);
588 | }
589 | }
590 |
591 | private void ImageContainer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
592 | {
593 | if (IsPanningEnabled)
594 | {
595 | _mouseMoveStartPoint = e.GetPosition(InteropHost);
596 | }
597 | }
598 |
599 | private void ImageContainer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
600 | {
601 | if (!IsSelectionEnabled)
602 | {
603 | return;
604 | }
605 |
606 | if (!_isPanning && Shapes != null)
607 | {
608 | var mousePosition = e.GetPosition(InteropHost);
609 | var testPoint = new Vector2((float)mousePosition.X, (float)mousePosition.Y);
610 | IShape selectedShape = null;
611 |
612 | //do a hit test to see what shape is being clicked on
613 | foreach (var shape in Shapes)
614 | {
615 | var pathGeometry = _createdGeometries[shape.GeometryHash];
616 |
617 | var translation = Matrix3x2.Identity;
618 | if (pathGeometry is GeometryPath)
619 | {
620 | var vectorShape = shape as VectorShape;
621 | if (vectorShape != null)
622 | {
623 | translation = pathGeometry.GetRenderTransform(vectorShape.Scaling, vectorShape.PixelXLocation, vectorShape.PixelYLocation, vectorShape.Rotation, RenderOrigin);
624 |
625 | if (pathGeometry.Geometry.FillContainsPoint(testPoint, translation, 4f))
626 | {
627 | var previousSelectedShape = Shapes.FirstOrDefault(s => s.IsSelected);
628 | if (previousSelectedShape != null)
629 | {
630 | previousSelectedShape.IsSelected = false;
631 | }
632 |
633 | shape.IsSelected = true;
634 | selectedShape = shape;
635 | }
636 | else
637 | {
638 | shape.IsSelected = false;
639 | }
640 | }
641 | }
642 | else if (pathGeometry is LineGeometry)
643 | {
644 | if (pathGeometry.Geometry.StrokeContainsPoint(testPoint, shape.StrokeWidth, _lineStrokeStyle, translation, 4f))
645 | {
646 | var previousSelectedShape = Shapes.FirstOrDefault(s => s.IsSelected);
647 | if (previousSelectedShape != null)
648 | {
649 | previousSelectedShape.IsSelected = false;
650 | }
651 |
652 | shape.IsSelected = true;
653 | selectedShape = shape;
654 | }
655 | else
656 | {
657 | shape.IsSelected = false;
658 | }
659 | }
660 | }
661 |
662 | SelectedShape = selectedShape;
663 | InteropImage.RequestRender();
664 | }
665 |
666 | _isPanning = false;
667 | }
668 |
669 | private void InteropImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
670 | {
671 | if (!(e.NewValue is bool) || !(e.OldValue is bool))
672 | {
673 | return;
674 | }
675 |
676 | if (!(bool)e.NewValue && (bool)e.OldValue)
677 | {
678 | _renderRequiresInit = true;
679 |
680 | }
681 | else
682 | {
683 | InteropImage.RequestRender();
684 | }
685 | }
686 | }
687 | }
688 |
--------------------------------------------------------------------------------
/WpfDirect2d/Enums.cs:
--------------------------------------------------------------------------------
1 | namespace WpfDirect2D
2 | {
3 | public enum ShapeRenderOrigin
4 | {
5 | ///
6 | /// Render the shape with the desired location at the center of the shape
7 | ///
8 | Center,
9 |
10 | ///
11 | /// Render the shape with the desired location at the top left corner of the shape
12 | ///
13 | TopLeft
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/WpfDirect2d/Extensions.cs:
--------------------------------------------------------------------------------
1 | using SharpDX;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using Wpf = System.Windows.Media;
6 | using Windows= System.Windows;
7 | using SharpDX.Mathematics.Interop;
8 |
9 | namespace WpfDirect2D
10 | {
11 | internal static class Extensions
12 | {
13 | public static Color ToDirect2dColor(this Wpf.Color wpfColor)
14 | {
15 | return new Color(wpfColor.R, wpfColor.G, wpfColor.B, wpfColor.A);
16 | }
17 |
18 | public static RawVector2 ToRawVector2(this Windows.Point point)
19 | {
20 | var rawVector = new RawVector2()
21 | {
22 | X = Convert.ToSingle(point.X),
23 | Y = Convert.ToSingle(point.Y)
24 | };
25 | return rawVector;
26 | }
27 |
28 | public static RawVector2[] ToRawVector2Array(this List points)
29 | {
30 | List rawVectors = new List();
31 | foreach (var wPoint in points)
32 | {
33 | rawVectors.Add(wPoint.ToRawVector2());
34 | }
35 |
36 | return rawVectors.ToArray();
37 | }
38 |
39 | public static int GetSequenceHashCode(this IList sequence)
40 | {
41 | const int seed = 487;
42 | const int modifier = 31;
43 |
44 | unchecked
45 | {
46 | return sequence.Aggregate(seed, (current, item) =>
47 | (current * modifier) + item.GetHashCode());
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WpfDirect2d/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Microsoft.Wpf.Interop.DirectX
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WpfDirect2d/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("WpfDirect2d")]
9 | [assembly: AssemblyDescription("WPF Direct2D Surface")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Lucas Christinson")]
12 | [assembly: AssemblyProduct("WpfDirect2d")]
13 | [assembly: AssemblyCopyright("Copyright © 2017")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("24f3bfba-5305-4773-816a-0acb2b968992")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 | [assembly: AssemblyInformationalVersion("1.0.0.0")]
--------------------------------------------------------------------------------
/WpfDirect2d/Shapes/BaseGeometry.cs:
--------------------------------------------------------------------------------
1 | using SharpDX.Direct2D1;
2 | using System;
3 | using SharpDX;
4 | using SharpDX.Mathematics.Interop;
5 |
6 | namespace WpfDirect2D.Shapes
7 | {
8 | internal abstract class BaseGeometry : IDisposable
9 | {
10 | private bool _disposedValue = false; // To detect redundant calls
11 |
12 | protected BaseGeometry(PathGeometry geometry)
13 | {
14 | Geometry = geometry;
15 | }
16 |
17 | public PathGeometry Geometry { get; protected set; }
18 |
19 | public int GeometryHash { get; protected set; }
20 |
21 | public GeometryRealization FilledRealization { get; protected set; }
22 |
23 | public GeometryRealization StrokedRealization { get; protected set; }
24 |
25 | protected abstract void SetGeometryHash();
26 |
27 | public abstract void CreateRealizations(DeviceContext1 deviceContext);
28 |
29 | public RawRectangleF GetBounds(Matrix3x2 scaleTransform)
30 | {
31 | return Geometry.GetBounds(scaleTransform);
32 | }
33 |
34 | public Matrix3x2 GetRenderTransform(float scaleFactor, float xLocation, float yLocation, float rotation, ShapeRenderOrigin renderOrigin)
35 | {
36 | if (renderOrigin == ShapeRenderOrigin.Center)
37 | {
38 | return GetCenterRenderTransform(scaleFactor, xLocation, yLocation, rotation);
39 | }
40 |
41 | return GetTopLeftRenderTransform(scaleFactor, xLocation, yLocation, rotation);
42 | }
43 |
44 | private Matrix3x2 GetCenterRenderTransform(float scaleFactor, float xLocation, float yLocation, float rotation)
45 | {
46 | var scaleTransform = Matrix3x2.Scaling(scaleFactor);
47 | var geometryBounds = GetBounds(scaleTransform);
48 | float centerScalingOffset = scaleFactor * 4;
49 | float xTranslate = xLocation - (geometryBounds.Right - geometryBounds.Left) + centerScalingOffset;
50 | float yTranslate = yLocation - (geometryBounds.Bottom - geometryBounds.Top) + centerScalingOffset;
51 |
52 | return scaleTransform * Matrix3x2.Rotation(rotation) * Matrix3x2.Translation(xTranslate, yTranslate);
53 | }
54 |
55 | private Matrix3x2 GetTopLeftRenderTransform(float scaleFactor, float xLocation, float yLocation, float rotation)
56 | {
57 | return Matrix3x2.Scaling(scaleFactor) * Matrix3x2.Rotation(rotation) * Matrix3x2.Translation(xLocation, yLocation);
58 | }
59 |
60 | protected virtual void Dispose(bool disposing)
61 | {
62 | if (!_disposedValue)
63 | {
64 | if (disposing)
65 | {
66 | Geometry?.Dispose();
67 | FilledRealization?.Dispose();
68 | StrokedRealization?.Dispose();
69 | }
70 |
71 | _disposedValue = true;
72 | }
73 | }
74 |
75 | // This code added to correctly implement the disposable pattern.
76 | public void Dispose()
77 | {
78 | // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
79 | Dispose(true);
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/WpfDirect2d/Shapes/GeometryPath.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using SharpDX;
3 | using SharpDX.Direct2D1;
4 | using SharpDX.Mathematics.Interop;
5 |
6 | namespace WpfDirect2D.Shapes
7 | {
8 | internal class GeometryPath : BaseGeometry
9 | {
10 | private string _path;
11 |
12 | public GeometryPath(string geometryPath, PathGeometry geometry) : base(geometry)
13 | {
14 | Path = geometryPath;
15 | }
16 |
17 | public string Path
18 | {
19 | get { return _path; }
20 | set
21 | {
22 | _path = value;
23 | SetGeometryHash();
24 | }
25 | }
26 |
27 | protected override void SetGeometryHash()
28 | {
29 | GeometryHash = Path.GetHashCode();
30 | }
31 |
32 | public override void CreateRealizations(DeviceContext1 deviceContext)
33 | {
34 | var matrix = new RawMatrix3x2(Matrix3x2.Identity.M11, Matrix3x2.Identity.M12, Matrix3x2.Identity.M21, Matrix3x2.Identity.M22, Matrix3x2.Identity.M31, Matrix3x2.Identity.M32);
35 | var tolerance = D2D1.ComputeFlatteningTolerance(ref matrix, maxZoomFactor: 4);
36 | FilledRealization = new GeometryRealization(deviceContext, Geometry, tolerance);
37 | StrokedRealization = new GeometryRealization(deviceContext, Geometry, tolerance, 1f, null);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/WpfDirect2d/Shapes/IShape.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Windows.Media;
3 |
4 | namespace WpfDirect2D.Shapes
5 | {
6 | public interface IShape
7 | {
8 | ///
9 | /// Is this shape instance valid, i.e. is the necessary data provided to render the shape
10 | ///
11 | bool IsValid { get; }
12 |
13 | ///
14 | /// Fill color to use when rendering this shape
15 | ///
16 | Color FillColor { get; set; }
17 |
18 | ///
19 | /// Stroke color to use when rendering this shape
20 | ///
21 | Color StrokeColor { get; set; }
22 |
23 | ///
24 | /// Stroke width
25 | ///
26 | float StrokeWidth { get; set; }
27 |
28 | ///
29 | /// Is this shape selected
30 | ///
31 | bool IsSelected { get; set; }
32 |
33 | ///
34 | /// Color to set shape as when it is selected
35 | ///
36 | Color SelectedColor { get; set; }
37 |
38 | ///
39 | /// Additional colors to cache
40 | ///
41 | List BrushColorsToCache { get; }
42 |
43 | ///
44 | /// Get a list of all the colors this shape could use
45 | ///
46 | ///
47 | List GetColorsToCache();
48 |
49 | int GeometryHash { get; set; }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WpfDirect2d/Shapes/LineGeometry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using SharpDX.Direct2D1;
3 | using System.Collections.Generic;
4 |
5 | namespace WpfDirect2D.Shapes
6 | {
7 | internal class LineGeometry : BaseGeometry
8 | {
9 | public LineGeometry(List lineNodes, PathGeometry geometry)
10 | : base(geometry)
11 | {
12 | LineNodes = lineNodes;
13 | SetGeometryHash();
14 | }
15 |
16 | public List LineNodes { get; }
17 |
18 | protected sealed override void SetGeometryHash()
19 | {
20 | GeometryHash = LineNodes.GetSequenceHashCode();
21 | }
22 |
23 | public override void CreateRealizations(DeviceContext1 deviceContext)
24 | {
25 | //lines do not support realizations at this time
26 | throw new NotImplementedException();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/WpfDirect2d/Shapes/LineShape.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Windows;
4 | using System.Windows.Media;
5 |
6 | namespace WpfDirect2D.Shapes
7 | {
8 | public class LineShape : IShape
9 | {
10 | public LineShape()
11 | {
12 | LineNodes = new List();
13 | BrushColorsToCache = new List();
14 | }
15 |
16 | public bool IsValid => LineNodes.Count >= 2;
17 |
18 | public Color FillColor { get; set; }
19 |
20 | public Color StrokeColor { get; set; }
21 |
22 | public float StrokeWidth { get; set; }
23 |
24 | public bool IsSelected { get; set; }
25 |
26 | public Color SelectedColor { get; set; }
27 |
28 | public List LineNodes { get; }
29 |
30 | public bool IsLineClosed { get; set; }
31 |
32 | public List BrushColorsToCache { get; }
33 |
34 | public int GeometryHash { get; set; }
35 |
36 | public Point GetStartingPoint()
37 | {
38 | return LineNodes.FirstOrDefault();
39 | }
40 |
41 | ///
42 | /// Get the list of points to connect to the starting point.
43 | ///
44 | ///
45 | public List GetConnectingPoints()
46 | {
47 | if (!IsValid)
48 | {
49 | return null;
50 | }
51 |
52 | return LineNodes.Skip(1).ToList();
53 | }
54 |
55 | public List GetColorsToCache()
56 | {
57 | //make sure the stoke, fill, and selected colors are in the list
58 | if (!BrushColorsToCache.Contains(FillColor))
59 | {
60 | BrushColorsToCache.Add(FillColor);
61 | }
62 |
63 | if (!BrushColorsToCache.Contains(StrokeColor))
64 | {
65 | BrushColorsToCache.Add(StrokeColor);
66 | }
67 |
68 | if (!BrushColorsToCache.Contains(SelectedColor))
69 | {
70 | BrushColorsToCache.Add(SelectedColor);
71 | }
72 |
73 | return BrushColorsToCache;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/WpfDirect2d/Shapes/VectorShape.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Windows.Media;
3 |
4 | namespace WpfDirect2D.Shapes
5 | {
6 | public class VectorShape : IShape
7 | {
8 | private string _geometryPath;
9 |
10 | public VectorShape()
11 | {
12 | BrushColorsToCache = new List();
13 | }
14 |
15 | public bool IsValid => !string.IsNullOrEmpty(GeometryPath);
16 |
17 | ///
18 | /// Path describing the geometry in svg / xaml path format
19 | ///
20 | public string GeometryPath
21 | {
22 | get { return _geometryPath; }
23 | set
24 | {
25 | _geometryPath = value;
26 | GeometryHash = GeometryPath.GetHashCode();
27 | }
28 | }
29 |
30 | public float PixelXLocation { get; set; }
31 |
32 | public float PixelYLocation { get; set; }
33 |
34 | public float Rotation { get; set; }
35 |
36 | public float Scaling { get; set; }
37 |
38 | public Color FillColor { get; set; }
39 |
40 | public Color StrokeColor { get; set; }
41 |
42 | public float StrokeWidth { get; set; }
43 |
44 | public bool IsSelected { get; set; }
45 |
46 | public Color SelectedColor { get; set; }
47 |
48 | public List BrushColorsToCache { get; }
49 |
50 | public int GeometryHash { get; set; }
51 |
52 | public List GetColorsToCache()
53 | {
54 | //make sure the stroke, fill, and selected colors are in the list
55 | if (!BrushColorsToCache.Contains(FillColor))
56 | {
57 | BrushColorsToCache.Add(FillColor);
58 | }
59 |
60 | if (!BrushColorsToCache.Contains(StrokeColor))
61 | {
62 | BrushColorsToCache.Add(StrokeColor);
63 | }
64 |
65 | if (!BrushColorsToCache.Contains(SelectedColor))
66 | {
67 | BrushColorsToCache.Add(SelectedColor);
68 | }
69 |
70 | return BrushColorsToCache;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/WpfDirect2d/WpfDirect2D.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WPF.Direct2D.Surface
5 | $version$
6 | $title$
7 | $author$
8 | $author$
9 | false
10 | $description$
11 | Copyright 2017
12 | https://github.com/ljchristinson/WPFDirect2D
13 | https://raw.githubusercontent.com/ljchristinson/WPFDirect2D/master/LICENSE
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/WpfDirect2d/WpfDirect2d.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {24F3BFBA-5305-4773-816A-0ACB2B968992}
8 | Library
9 | Properties
10 | WpfDirect2D
11 | WpfDirect2D
12 | v4.5
13 | 512
14 |
15 |
16 |
17 |
18 |
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 | x64
27 | false
28 |
29 |
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 | true
39 | bin\x64\Debug\
40 | DEBUG;TRACE
41 | full
42 | x64
43 | prompt
44 | MinimumRecommendedRules.ruleset
45 |
46 |
47 | bin\x64\Release\
48 | TRACE
49 | true
50 | pdbonly
51 | x64
52 | prompt
53 | MinimumRecommendedRules.ruleset
54 |
55 |
56 |
57 | ..\packages\Costura.Fody.1.6.2\lib\dotnet\Costura.dll
58 | False
59 |
60 |
61 | ..\packages\Microsoft.Wpf.Interop.DirectX-x64.0.9.0-beta-22856\lib\net45\Microsoft.Wpf.Interop.DirectX.dll
62 |
63 |
64 |
65 |
66 | ..\packages\SharpDX.4.0.1\lib\net45\SharpDX.dll
67 |
68 |
69 | ..\packages\SharpDX.Direct2D1.4.0.1\lib\net45\SharpDX.Direct2D1.dll
70 |
71 |
72 | ..\packages\SharpDX.Direct3D11.4.0.1\lib\net45\SharpDX.Direct3D11.dll
73 |
74 |
75 | ..\packages\SharpDX.DXGI.4.0.1\lib\net45\SharpDX.DXGI.dll
76 |
77 |
78 | ..\packages\SharpDX.Mathematics.4.0.1\lib\net45\SharpDX.Mathematics.dll
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | Direct2DSurface.xaml
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | Designer
108 | MSBuild:Compile
109 |
110 |
111 |
112 |
113 | Designer
114 |
115 |
116 |
117 |
118 | {25b25f53-7409-40df-89ba-ec33d7b00de0}
119 | VectorGraphicsHelper
120 |
121 |
122 |
123 |
124 | Designer
125 |
126 |
127 |
128 |
129 |
130 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
147 |
--------------------------------------------------------------------------------
/WpfDirect2d/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------