├── .gitignore
├── LICENSE
├── README.md
└── Source
├── .editorconfig
├── CodeSigning
└── sign_a_file.bat
├── DXControl.sln
├── DXControl
├── D2Phap.DXControl.csproj
├── DXCanvas.cs
├── DXGraphics.cs
├── DXHelper.cs
├── Enums.cs
└── Events.cs
└── Demo
├── App.config
├── Demo.csproj
├── DemoCanvas.cs
├── Form1.Designer.cs
├── Form1.cs
├── Form1.resx
├── Program.cs
├── Properties
└── launchSettings.json
├── app.manifest
└── photo.png
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 - 2025 DUONG DIEU PHAP
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 | # D2Phap.DXControl
2 | - A WinForms control that supports drawing with Direct2D thanks to [WicNet](https://github.com/smourier/WicNet).
3 | - This control has been used in [ImageGlass](https://github.com/d2phap/ImageGlass) software since version 9.0.
4 |
5 | 
6 |
7 |
8 | ## Resource links
9 | - Nuget package: [https://www.nuget.org/packages/D2Phap.DXControl](https://www.nuget.org/packages/D2Phap.DXControl)
10 | - Project url: [https://github.com/d2phap/DXControl](https://github.com/d2phap/DXControl)
11 | - About: [https://imageglass.org/about](https://imageglass.org/about)
12 |
13 |
14 | ## Features
15 | - High performance drawing using Direct2D.
16 | - Names and types are exactly the same as the native concepts of Direct2D (interfaces, enums, structures, constants, methods, arguments, guids, etc...). So you can read the official documentation, use existing C/C++ samples, and start coding with .NET right away.
17 | - All native COM interfaces are generated as .NET (COM) interfaces, this makes .NET programming easier, but they are not strictly needed.
18 | - Option to use Software or Hardware render target
19 | - Supports animation drawing with Direct2D.
20 |
21 | ## Requirements:
22 | - .NET 6.0, 7.0, 8.0
23 |
24 | ## Installation
25 | Run the command
26 | ```bash
27 | Install-Package D2Phap.DXControl
28 | ```
29 |
30 |
31 | ## Example
32 |
33 |
34 |
35 |
36 | Draws a rectangle, then moves it to the right side.
37 |
38 | ```cs
39 | using D2Phap.DXControl;
40 |
41 | // create a WinForms custom control that extends from DXCanvas
42 | public class DemoCanvas : DXCanvas
43 | {
44 | private RectangleF animatableRectangle = new(100, 100, 400, 200);
45 |
46 | public DemoCanvas()
47 | {
48 | EnableAnimation = true;
49 | UseHardwareAcceleration = true;
50 | }
51 |
52 | protected override void OnRender(DXGraphics g)
53 | {
54 | // draw a yellow rectangle with green border
55 | g.FillRectangle(rectText, Color.FromArgb(100, Yellow));
56 | g.DrawRectangle(rectText, Color.Green);
57 | }
58 |
59 | // Update frame logics for animation
60 | protected override void OnFrame(FrameEventArgs e)
61 | {
62 | // animate the rectangle to the right
63 | animatableRectangle.left++;
64 | }
65 | }
66 |
67 | ```
68 |
69 | See Demo project for full details.
70 |
71 | ## License
72 | [MIT](LICENSE)
73 |
74 | ## Support this project
75 | - [GitHub sponsor](https://github.com/sponsors/d2phap)
76 | - [Patreon](https://www.patreon.com/d2phap)
77 | - [PayPal](https://www.paypal.me/d2phap)
78 |
79 | Thanks for your gratitude and finance help!
80 |
81 |
--------------------------------------------------------------------------------
/Source/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # CS1591: Missing XML comment for publicly visible type or member
4 | dotnet_diagnostic.CS1591.severity = silent
5 |
6 | # CA2010: Always consume the value returned by methods marked with PreserveSigAttribute
7 | dotnet_diagnostic.CA2010.severity = silent
8 |
--------------------------------------------------------------------------------
/Source/CodeSigning/sign_a_file.bat:
--------------------------------------------------------------------------------
1 |
2 | @echo off
3 |
4 | set FILE=%1
5 |
6 | echo:
7 | echo *********************************************************************
8 | echo * ImageGlass Code Signing tool v8.4
9 | echo * https://imageglass.org
10 | echo *
11 | echo * https://www.ssl.com/how-to/using-your-code-signing-certificate
12 | echo *********************************************************************
13 | echo:
14 | echo:
15 |
16 |
17 | :: "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe"
18 | set TOOL="C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe"
19 |
20 | call %TOOL% sign /fd sha256 /tr http://ts.ssl.com /td sha256 /n "Duong Dieu Phap" /a %FILE%
21 | call %TOOL% verify /pa %FILE%
22 |
23 |
24 | echo:
25 | echo:
26 | pause
27 |
--------------------------------------------------------------------------------
/Source/DXControl.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32714.290
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo", "Demo\Demo.csproj", "{BF844B5A-26A8-4059-98C7-285959773DD2}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D2Phap.DXControl", "DXControl\D2Phap.DXControl.csproj", "{7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{30CD3659-C6B7-4AB5-BB3A-8FFC32072FE5}"
11 | ProjectSection(SolutionItems) = preProject
12 | .editorconfig = .editorconfig
13 | EndProjectSection
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Debug|x64 = Debug|x64
19 | Debug|x86 = Debug|x86
20 | Release|Any CPU = Release|Any CPU
21 | Release|x64 = Release|x64
22 | Release|x86 = Release|x86
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Debug|x64.ActiveCfg = Debug|x64
28 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Debug|x64.Build.0 = Debug|x64
29 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Debug|x86.ActiveCfg = Debug|x86
30 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Debug|x86.Build.0 = Debug|x86
31 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Release|x64.ActiveCfg = Release|x64
34 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Release|x64.Build.0 = Release|x64
35 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Release|x86.ActiveCfg = Release|x86
36 | {BF844B5A-26A8-4059-98C7-285959773DD2}.Release|x86.Build.0 = Release|x86
37 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Debug|x64.ActiveCfg = Debug|Any CPU
40 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Debug|x64.Build.0 = Debug|Any CPU
41 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Debug|x86.ActiveCfg = Debug|Any CPU
42 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Debug|x86.Build.0 = Debug|Any CPU
43 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Release|x64.ActiveCfg = Release|Any CPU
46 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Release|x64.Build.0 = Release|Any CPU
47 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Release|x86.ActiveCfg = Release|Any CPU
48 | {7DB72718-F0A3-41FC-8C8C-0E6E0EE0F6DA}.Release|x86.Build.0 = Release|Any CPU
49 | EndGlobalSection
50 | GlobalSection(SolutionProperties) = preSolution
51 | HideSolutionNode = FALSE
52 | EndGlobalSection
53 | GlobalSection(ExtensibilityGlobals) = postSolution
54 | SolutionGuid = {9E58C03A-07C3-43CA-A6F2-F36F5869727E}
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/Source/DXControl/D2Phap.DXControl.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0-windows;net7.0-windows;net8.0-windows
5 | enable
6 | true
7 | enable
8 | $(AssemblyName)
9 | D2Phap
10 | True
11 | WinForms control that supports drawing with Direct2D
12 | D2Phap.$(AssemblyName)
13 |
14 | A WinForms control that supports drawing with Direct2D thanks to WicNet. This control has being used in https://github.com/d2phap/ImageGlass since version 9.
15 |
16 | Copyright (C) 2022 - 2025 Duong Dieu Phap. All rights reserved.
17 | https://github.com/d2phap/DXControl
18 | README.md
19 | https://github.com/d2phap/DXControl
20 | Direct2D, WIC, DirectWrite, WinForms, DirectN, WicNet
21 | LICENSE
22 | True
23 | snupkg
24 | See release notes here: https://github.com/d2phap/DXControl/releases
25 | Duong Dieu Phap
26 | 4.1.0
27 | true
28 | true
29 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
30 | True
31 | $(MSBuildProjectName)
32 | latest
33 |
34 |
35 |
36 | embedded
37 |
38 |
39 |
40 | embedded
41 |
42 |
43 |
44 | portable
45 |
46 |
47 |
48 | portable
49 |
50 |
51 |
52 | portable
53 |
54 |
55 |
56 | portable
57 |
58 |
59 |
60 | portable
61 |
62 |
63 |
64 | portable
65 |
66 |
67 |
68 |
69 |
70 | True
71 | \
72 |
73 |
74 | True
75 | \
76 |
77 |
78 |
79 |
80 |
81 |
82 | all
83 | runtime; build; native; contentfiles; analyzers; buildtransitive
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/Source/DXControl/DXCanvas.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 | Copyright (C) 2022-2025 DUONG DIEU PHAP
4 | Project & license info: https://github.com/d2phap/DXControl
5 | */
6 | using DirectN;
7 | using System.ComponentModel;
8 | using WicNet.Utilities;
9 |
10 | namespace D2Phap.DXControl;
11 |
12 |
13 | ///
14 | /// Defines the base class for hybrid control with Direct2D and GDI+ graphics support.
15 | ///
16 | public class DXCanvas : Control
17 | {
18 | // Internal properties
19 | #region Internal properties
20 |
21 | protected bool _isControlLoaded = false;
22 | protected float _dpi = 96.0f;
23 | protected readonly PeriodicTimer _timer = new(TimeSpan.FromMilliseconds(10));
24 |
25 | // Protected properties
26 | protected IComObject? _d2DFactory = null;
27 | protected IComObject? _dWriteFactory = null;
28 | protected IComObject? _renderTarget;
29 | protected IComObject? _device;
30 | protected DXGraphics? _graphicsD2d;
31 |
32 |
33 | protected bool _useHardwardAcceleration = true;
34 | protected bool _firstPaintBackground = true;
35 | protected int _currentFps = 0;
36 | protected int _lastFps = 0;
37 | protected DateTime _lastFpsUpdate = DateTime.UtcNow;
38 |
39 | const uint D2DERR_RECREATE_TARGET = 0x8899000C;
40 |
41 | #endregion // Internal properties
42 |
43 |
44 | // Public properties
45 | #region Public properties
46 |
47 | ///
48 | /// Gets Direct2D factory.
49 | ///
50 | [Browsable(false)]
51 | public IComObject? Direct2DFactory => _d2DFactory;
52 |
53 |
54 | ///
55 | /// Gets DirectWrite factory.
56 | ///
57 | [Browsable(false)]
58 | public IComObject? DirectWriteFactory => _dWriteFactory;
59 |
60 |
61 | ///
62 | /// Gets render target for this control.
63 | ///
64 | [Browsable(false)]
65 | public IComObject? RenderTarget => _renderTarget;
66 |
67 |
68 | ///
69 | /// Gets Direct2D device context.
70 | ///
71 | [Browsable(false)]
72 | public IComObject Device => _device!;
73 |
74 |
75 | ///
76 | /// Gets the object used to draw in .
77 | ///
78 | [Browsable(false)]
79 | public DXGraphics? D2Graphics => _graphicsD2d;
80 |
81 |
82 | ///
83 | /// Gets the value indicates if control is fully loaded
84 | ///
85 | [Browsable(false)]
86 | public bool IsReady => !DesignMode && Created;
87 |
88 |
89 | ///
90 | /// Gets, sets the DPI for drawing when using .
91 | ///
92 | [Browsable(false)]
93 | public float BaseDpi
94 | {
95 | get => _dpi;
96 | set
97 | {
98 | if (_device == null) return;
99 |
100 | _dpi = value;
101 | _device.Object.SetDpi(_dpi, _dpi);
102 | }
103 | }
104 |
105 |
106 | ///
107 | /// Gets, sets a value indicating whether this control should draw its surface
108 | /// using Direct2D or GDI+.
109 | ///
110 | [Category("Graphics")]
111 | [DefaultValue(true)]
112 | public virtual bool UseHardwareAcceleration
113 | {
114 | get => _useHardwardAcceleration;
115 | set
116 | {
117 | var enableAnimationOldValue = EnableAnimation;
118 | var hasChange = _useHardwardAcceleration != value;
119 |
120 | _useHardwardAcceleration = value;
121 |
122 | // re-create device
123 | if (hasChange)
124 | {
125 | EnableAnimation = false;
126 | CreateDevice(DeviceCreatedReason.UseHardwareAccelerationChanged);
127 |
128 | EnableAnimation = enableAnimationOldValue;
129 | }
130 | }
131 | }
132 |
133 |
134 | ///
135 | /// Request to update logics of the current frame in the event.
136 | ///
137 | [Browsable(false)]
138 | public bool RequestUpdateFrame { get; set; } = false;
139 |
140 |
141 | ///
142 | /// Enables animation support for the control.
143 | ///
144 | [Category("Animation")]
145 | [DefaultValue(true)]
146 | public bool EnableAnimation { get; set; } = true;
147 |
148 |
149 | ///
150 | /// Enable FPS measurement.
151 | ///
152 | [Browsable(false)]
153 | public bool CheckFPS { get; set; } = false;
154 |
155 |
156 | ///
157 | /// Gets FPS info when the is set to true.
158 | ///
159 | [Browsable(false)]
160 | public int FPS => _lastFps;
161 |
162 |
163 | ///
164 | /// Occurs when the control is loaded and ready to use.
165 | ///
166 | public event EventHandler? Loaded;
167 |
168 |
169 | ///
170 | /// Occurs when the Device is created.
171 | ///
172 | public event EventHandler? DeviceCreated;
173 |
174 |
175 | ///
176 | /// Occurs when the control is being rendered by .
177 | ///
178 | public event EventHandler? Render;
179 |
180 |
181 | ///
182 | /// Occurs when the animation frame logics need to update.
183 | ///
184 | public event EventHandler? Frame;
185 |
186 | #endregion
187 |
188 |
189 | ///
190 | /// Initializes new instance of .
191 | ///
192 | public DXCanvas()
193 | {
194 | SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
195 | }
196 |
197 |
198 | // New / Virtual functions
199 | #region New / Virtual functions
200 |
201 | ///
202 | /// Initializes value for and .
203 | ///
204 | protected virtual void CreateFactories()
205 | {
206 | _d2DFactory = D2D1Functions.D2D1CreateFactory(D2D1_FACTORY_TYPE.D2D1_FACTORY_TYPE_SINGLE_THREADED);
207 |
208 | _dWriteFactory = DWriteFunctions.DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_SHARED);
209 | }
210 |
211 |
212 | ///
213 | /// Disposes and .
214 | ///
215 | protected virtual void DisposeFactories()
216 | {
217 | _d2DFactory?.Dispose();
218 | _d2DFactory = null;
219 |
220 | _dWriteFactory?.Dispose();
221 | _dWriteFactory = null;
222 | }
223 |
224 |
225 | ///
226 | /// Initializes value for , and .
227 | ///
228 | protected virtual void CreateDevice(DeviceCreatedReason reason)
229 | {
230 | // dispose the current device
231 | DisposeDevice();
232 |
233 | if (_d2DFactory == null)
234 | {
235 | throw new NullReferenceException($"{nameof(Direct2DFactory)} is null");
236 | }
237 | if (_dWriteFactory == null)
238 | {
239 | throw new NullReferenceException($"{nameof(DirectWriteFactory)} is null");
240 | }
241 |
242 |
243 | var rtType = UseHardwareAcceleration
244 | ? D2D1_RENDER_TARGET_TYPE.D2D1_RENDER_TARGET_TYPE_DEFAULT
245 | : D2D1_RENDER_TARGET_TYPE.D2D1_RENDER_TARGET_TYPE_SOFTWARE;
246 | var renderTargetProps = new D2D1_RENDER_TARGET_PROPERTIES()
247 | {
248 | dpiX = _dpi,
249 | dpiY = _dpi,
250 | type = rtType,
251 | minLevel = D2D1_FEATURE_LEVEL.D2D1_FEATURE_LEVEL_DEFAULT,
252 | pixelFormat = new D2D1_PIXEL_FORMAT()
253 | {
254 | alphaMode = D2D1_ALPHA_MODE.D2D1_ALPHA_MODE_PREMULTIPLIED,
255 | format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
256 | },
257 | };
258 |
259 | var hwndRenderTargetProps = new D2D1_HWND_RENDER_TARGET_PROPERTIES()
260 | {
261 | hwnd = Handle,
262 | pixelSize = new D2D_SIZE_U((uint)Width, (uint)Height),
263 | presentOptions = D2D1_PRESENT_OPTIONS.D2D1_PRESENT_OPTIONS_IMMEDIATELY,
264 | };
265 |
266 |
267 | // create new render target
268 | _renderTarget = _d2DFactory.CreateHwndRenderTarget(hwndRenderTargetProps, renderTargetProps);
269 | _renderTarget.Object.SetAntialiasMode(D2D1_ANTIALIAS_MODE.D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
270 | _renderTarget.Object.SetDpi(BaseDpi, BaseDpi);
271 | _renderTarget.Resize(new((uint)ClientSize.Width, (uint)ClientSize.Height));
272 |
273 |
274 | // create devide and graphics
275 | _device = _renderTarget.AsComObject();
276 | _graphicsD2d = new DXGraphics(_device, _d2DFactory, _dWriteFactory);
277 |
278 |
279 | OnDeviceCreated(reason);
280 | }
281 |
282 |
283 | ///
284 | /// Disposes and objects.
285 | ///
286 | protected virtual void DisposeDevice()
287 | {
288 | _graphicsD2d?.Dispose();
289 | _graphicsD2d = null;
290 |
291 | _device?.Dispose();
292 | _device = null;
293 |
294 | _renderTarget?.Dispose();
295 | _renderTarget = null;
296 |
297 | GC.Collect();
298 | }
299 |
300 |
301 | ///
302 | /// Triggers event.
303 | ///
304 | protected virtual void OnDeviceCreated(DeviceCreatedReason reason)
305 | {
306 | DeviceCreated?.Invoke(this, new DeviceCreatedEventArgs(reason));
307 | }
308 |
309 |
310 | ///
311 | /// Triggers event to paint the control.
312 | ///
313 | protected virtual void OnRender(DXGraphics g)
314 | {
315 | if (!IsReady) return;
316 | Render?.Invoke(this, new(g));
317 | }
318 |
319 |
320 | ///
321 | /// Triggers event to process animation logic when frame changes.
322 | ///
323 | protected virtual void OnFrame(FrameEventArgs e)
324 | {
325 | if (!IsReady) return;
326 |
327 | Frame?.Invoke(this, e);
328 | }
329 |
330 |
331 | ///
332 | /// Triggers event when the control is ready.
333 | ///
334 | protected virtual void OnLoaded()
335 | {
336 | Loaded?.Invoke(this, new());
337 | }
338 |
339 |
340 | ///
341 | /// Invalidates client retangle of the control and causes a paint message to the control. This does not apply to child controls.
342 | ///
343 | public new void Invalidate()
344 | {
345 | Invalidate(false);
346 | }
347 |
348 | #endregion
349 |
350 |
351 |
352 | // Override functions
353 | #region Override functions
354 |
355 | protected override void CreateHandle()
356 | {
357 | base.CreateHandle();
358 | if (DesignMode) return;
359 |
360 | // disable GDI+
361 | DoubleBuffered = false;
362 |
363 | // create factories
364 | if (_d2DFactory == null || _dWriteFactory == null)
365 | {
366 | CreateFactories();
367 | }
368 |
369 | // create device
370 | if (_renderTarget == null || _device == null)
371 | {
372 | CreateDevice(DeviceCreatedReason.FirstTime);
373 | }
374 |
375 |
376 | // start framing timer
377 | _ = StartFramingTimerAsync();
378 | }
379 |
380 |
381 | protected override void DestroyHandle()
382 | {
383 | base.DestroyHandle();
384 |
385 | DisposeDevice();
386 | DisposeFactories();
387 | }
388 |
389 |
390 | protected override void Dispose(bool disposing)
391 | {
392 | base.Dispose(disposing);
393 |
394 | _timer.Dispose();
395 |
396 | _graphicsD2d?.Dispose();
397 | _graphicsD2d = null;
398 |
399 | // '_device' must be disposed in DestroyHandle()
400 | }
401 |
402 |
403 | protected override void WndProc(ref Message m)
404 | {
405 | const int WM_SIZE = 0x0005;
406 | const int WM_ERASEBKGND = 0x0014;
407 | const int WM_DESTROY = 0x0002;
408 |
409 | switch (m.Msg)
410 | {
411 | case WM_ERASEBKGND:
412 |
413 | // to fix background is delayed to paint on launch
414 | if (_firstPaintBackground)
415 | {
416 | _firstPaintBackground = false;
417 |
418 | _device?.BeginDraw();
419 | _device?.Clear(BackColor.ToD3DCOLORVALUE());
420 | _device?.EndDraw();
421 |
422 | //if (!_useHardwardAcceleration)
423 | //{
424 | // base.WndProc(ref m);
425 | //}
426 | //else
427 | //{
428 | // _device?.BeginDraw();
429 | // _device?.Clear(BackColor.ToD3DCOLORVALUE());
430 | // _device?.EndDraw();
431 | //}
432 | }
433 | break;
434 |
435 | case WM_SIZE:
436 | base.WndProc(ref m);
437 |
438 | _renderTarget?.Resize(new((uint)ClientSize.Width, (uint)ClientSize.Height));
439 | break;
440 |
441 | case WM_DESTROY:
442 | DisposeDevice();
443 | DisposeFactories();
444 |
445 | base.WndProc(ref m);
446 | break;
447 |
448 | default:
449 | base.WndProc(ref m);
450 | break;
451 | }
452 | }
453 |
454 |
455 | protected override void OnResize(EventArgs e)
456 | {
457 | base.OnResize(e);
458 | if (DesignMode) return;
459 |
460 |
461 | _renderTarget?.Resize(new((uint)ClientSize.Width, (uint)ClientSize.Height));
462 |
463 | // update the control once size/windows state changed
464 | ResizeRedraw = true;
465 | }
466 |
467 |
468 | protected override void OnSizeChanged(EventArgs e)
469 | {
470 | // detect if control is loaded
471 | if (!DesignMode && Created)
472 | {
473 | // control is loaded
474 | if (!_isControlLoaded)
475 | {
476 | _isControlLoaded = true;
477 |
478 | OnLoaded();
479 | }
480 |
481 | base.OnSizeChanged(e);
482 | }
483 | }
484 |
485 |
486 | protected override void OnPaintBackground(PaintEventArgs e)
487 | {
488 | // handled in OnPaint event
489 |
490 | // base.OnPaintBackground(e);
491 | }
492 |
493 |
494 | ///
495 | /// Do use if you want to draw on the control.
496 | ///
497 | protected override void OnPaint(PaintEventArgs e)
498 | {
499 | if (DesignMode)
500 | {
501 | e.Graphics.Clear(BackColor);
502 |
503 | using var brush = new SolidBrush(ForeColor);
504 | e.Graphics.DrawString(
505 | $"{GetType().FullName} does not support rendering in design mode.",
506 | Font, brush, 10, 10);
507 |
508 | return;
509 | }
510 |
511 |
512 | // draw
513 | if (_device != null && _graphicsD2d != null)
514 | {
515 | _device.BeginDraw();
516 | _device.Clear(BackColor.ToD3DCOLORVALUE());
517 | OnRender(_graphicsD2d);
518 |
519 | try
520 | {
521 | _device.EndDraw();
522 | }
523 | catch (Win32Exception ex)
524 | {
525 | // handle device lost
526 | if (ex.ErrorCode == unchecked((int)D2DERR_RECREATE_TARGET))
527 | {
528 | CreateDevice(DeviceCreatedReason.DeviceLost);
529 | }
530 | else
531 | {
532 | throw;
533 | }
534 | }
535 | }
536 |
537 |
538 | // calculate FPS
539 | if (CheckFPS)
540 | {
541 | if (_lastFpsUpdate.Second != DateTime.UtcNow.Second)
542 | {
543 | _lastFps = _currentFps;
544 | _currentFps = 0;
545 | _lastFpsUpdate = DateTime.UtcNow;
546 | }
547 | else
548 | {
549 | _currentFps++;
550 | }
551 | }
552 | }
553 |
554 |
555 | #endregion // Override functions
556 |
557 |
558 |
559 | // Private functions
560 | #region Private functions
561 |
562 | private async Task StartFramingTimerAsync()
563 | {
564 | while (await _timer.WaitForNextTickAsync())
565 | {
566 | if (EnableAnimation && RequestUpdateFrame)
567 | {
568 | var e = new FrameEventArgs();
569 |
570 | #if NET8_0_OR_GREATER
571 | e.Period = _timer.Period;
572 | #endif
573 |
574 | OnFrame(e);
575 | Invalidate();
576 |
577 | #if NET8_0_OR_GREATER
578 | _timer.Period = e.Period;
579 | #endif
580 | }
581 | }
582 | }
583 |
584 |
585 | #endregion // Private functions
586 |
587 |
588 | }
589 |
--------------------------------------------------------------------------------
/Source/DXControl/DXGraphics.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 | Copyright (C) 2022 - 2025 DUONG DIEU PHAP
4 | Project & license info: https://github.com/d2phap/DXControl
5 | */
6 | using DirectN;
7 | using System.Runtime.InteropServices;
8 |
9 | namespace D2Phap.DXControl;
10 |
11 |
12 | ///
13 | /// Encapsulates a Direct2D drawing surface.
14 | ///
15 | public class DXGraphics : IDisposable
16 | {
17 | #region IDisposable Disposing
18 |
19 | private bool _isDisposed = false;
20 |
21 | protected virtual void Dispose(bool disposing)
22 | {
23 | if (_isDisposed)
24 | return;
25 |
26 | if (disposing)
27 | {
28 | // Free any other managed objects here.
29 | }
30 |
31 | // Free any unmanaged objects here.
32 | _isDisposed = true;
33 | }
34 |
35 | public virtual void Dispose()
36 | {
37 | Dispose(true);
38 | GC.SuppressFinalize(this);
39 | }
40 |
41 | ~DXGraphics()
42 | {
43 | Dispose(false);
44 | }
45 |
46 | #endregion
47 |
48 |
49 | private bool _useAntialias = true;
50 |
51 |
52 | #region Public properties
53 |
54 | ///
55 | /// Gets, sets the value specifies whether smoothing (antialiasing) is applied
56 | /// to lines and curves and the edges of filled areas.
57 | ///
58 | public bool UseAntialias
59 | {
60 | get => _useAntialias;
61 | set
62 | {
63 | _useAntialias = value;
64 | if (DeviceContext == null) return;
65 |
66 | if (_useAntialias)
67 | {
68 | DeviceContext.Object.SetAntialiasMode(D2D1_ANTIALIAS_MODE.D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
69 | DeviceContext.Object.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE.D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
70 | }
71 | else
72 | {
73 | DeviceContext.Object.SetAntialiasMode(D2D1_ANTIALIAS_MODE.D2D1_ANTIALIAS_MODE_ALIASED);
74 | DeviceContext.Object.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE.D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
75 | }
76 | }
77 | }
78 |
79 |
80 | ///
81 | /// Gets the object used to control the drawing.
82 | ///
83 | public IComObject DeviceContext { get; init; }
84 |
85 |
86 | ///
87 | /// Gets the object wrapped into the for drawing.
88 | ///
89 | public IComObject D2DFactory { get; init; }
90 |
91 |
92 | ///
93 | /// Gets the object wrapped into the for drawing text.
94 | ///
95 | public IComObject DWriteFactory { get; init; }
96 |
97 | #endregion // Public properties
98 |
99 |
100 |
101 | ///
102 | /// Initialize new instance of .
103 | ///
104 | ///
105 | public DXGraphics(IComObject? dc, IComObject? d2dF, IComObject? wf)
106 | {
107 | if (dc == null)
108 | {
109 | throw new ArgumentNullException(nameof(dc), "ID2D1DeviceContext is null.");
110 | }
111 |
112 | if (d2dF == null)
113 | {
114 | throw new ArgumentNullException(nameof(d2dF), "IComObject is null.");
115 | }
116 |
117 | if (wf == null)
118 | {
119 | throw new ArgumentNullException(nameof(wf), "IComObject is null.");
120 | }
121 |
122 | DeviceContext = dc;
123 | D2DFactory = d2dF;
124 | DWriteFactory = wf;
125 | UseAntialias = true;
126 | }
127 |
128 |
129 |
130 | #region Draw bitmap
131 |
132 | ///
133 | /// Draws the specified bitmap after scaling it to the size of the specified rectangle.
134 | ///
135 | public void DrawBitmap(IComObject? bitmap,
136 | RectangleF? destRect = null, RectangleF? srcRect = null,
137 | InterpolationMode interpolation = InterpolationMode.NearestNeighbor,
138 | float opacity = 1)
139 | {
140 | if (bitmap is not IComObject bmp) return;
141 |
142 | D2D_RECT_F? sourceRect = null;
143 | D2D_RECT_F? destinationRect = null;
144 |
145 |
146 | if (srcRect != null)
147 | {
148 | sourceRect = new D2D_RECT_F(
149 | srcRect.Value.Left,
150 | srcRect.Value.Top,
151 | srcRect.Value.Right,
152 | srcRect.Value.Bottom);
153 | }
154 |
155 | if (destRect != null)
156 | {
157 | // Note: Bitmap will be scaled if the x, y are float numbers
158 | // https://github.com/d2phap/ImageGlass/issues/1786
159 | var left = (int)destRect.Value.Left;
160 | var top = (int)destRect.Value.Top;
161 | var right = left + destRect.Value.Width;
162 | var bottom = top + destRect.Value.Height;
163 |
164 | destinationRect = new D2D_RECT_F(left, top, right, bottom);
165 | }
166 |
167 |
168 | DeviceContext.DrawBitmap(bmp, opacity, (D2D1_INTERPOLATION_MODE)interpolation, destinationRect, sourceRect);
169 | }
170 |
171 |
172 | #endregion // Draw bitmap
173 |
174 |
175 | #region Draw/Fill ellipse
176 |
177 | ///
178 | /// Draws the outline and paints the interior of the specified ellipse.
179 | ///
180 | public void DrawEllipse(float x, float y, float radius, Color borderColor, Color? fillColor, float strokeWidth = 1)
181 | {
182 | var rect = new RectangleF(x - radius, y - radius, radius * 2, radius * 2);
183 |
184 | DrawEllipse(rect, borderColor, fillColor, strokeWidth);
185 | }
186 |
187 |
188 | ///
189 | /// Draws the outline and paints the interior of the specified ellipse.
190 | ///
191 | public void DrawEllipse(RectangleF rect, Color borderColor, Color? fillColor, float strokeWidth = 1)
192 | {
193 | DrawEllipse(rect.X, rect.Y, rect.Width, rect.Height, borderColor, fillColor, strokeWidth);
194 | }
195 |
196 |
197 | ///
198 | /// Draws the outline and paints the interior of the specified ellipse.
199 | ///
200 | public void DrawEllipse(float x, float y, float width, float height, Color borderColor, Color? fillColor, float strokeWidth = 1)
201 | {
202 | var ellipse = new D2D1_ELLIPSE(x + width / 2, y + height / 2, width / 2, height / 2);
203 |
204 | // draw background color -----------------------------------
205 | if (fillColor != null)
206 | {
207 | // create solid brush for background
208 | var bgColor = DXHelper.FromColor(fillColor.Value);
209 | using var bgBrush = DeviceContext.CreateSolidColorBrush(bgColor);
210 |
211 | // draw background
212 | DeviceContext.FillEllipse(ellipse, bgBrush);
213 | }
214 |
215 |
216 | // draw ellipse border ------------------------------------
217 | // create solid brush for border
218 | var bdColor = DXHelper.FromColor(borderColor);
219 | using var bdBrush = DeviceContext.CreateSolidColorBrush(bdColor);
220 |
221 | // draw border
222 | DeviceContext.DrawEllipse(ellipse, bdBrush, strokeWidth);
223 | }
224 |
225 | #endregion // Draw/Fill ellipse
226 |
227 |
228 | #region Draw/Fill Rectangle
229 |
230 | ///
231 | /// Draws the outline and paints the interior of the specified rectangle.
232 | ///
233 | public void DrawRectangle(float x, float y, float width, float height, float radius, Color borderColor, Color? fillColor = null, float strokeWidth = 1)
234 | {
235 | DrawRectangle(new RectangleF(x, y, width, height), radius, borderColor, fillColor, strokeWidth);
236 | }
237 |
238 | ///
239 | /// Draws the outline and paints the interior of the specified rectangle.
240 | ///
241 | public void DrawRectangle(RectangleF rect, float radius, Color borderColor, Color? fillColor = null, float strokeWidth = 1)
242 | {
243 | // create rounded rectangle
244 | var roundedRect = new D2D1_ROUNDED_RECT()
245 | {
246 | rect = new D2D_RECT_F(rect.Left, rect.Top, rect.Right, rect.Bottom),
247 | radiusX = radius,
248 | radiusY = radius,
249 | };
250 |
251 |
252 | // draw rectangle background -------------------------------
253 | if (fillColor != null)
254 | {
255 | // create solid brush for background
256 | var bgColor = DXHelper.FromColor(fillColor.Value);
257 | using var bgBrush = DeviceContext.CreateSolidColorBrush(bgColor);
258 |
259 | // draw background
260 | DeviceContext.FillRoundedRectangle(roundedRect, bgBrush);
261 | }
262 |
263 |
264 | // draw rectangle border ------------------------------------
265 | // create solid brush for border
266 | var bdColor = DXHelper.FromColor(borderColor);
267 | using var bdBrush = DeviceContext.CreateSolidColorBrush(bdColor);
268 |
269 | // draw border
270 | DeviceContext.DrawRoundedRectangle(roundedRect, bdBrush, strokeWidth);
271 | }
272 |
273 | #endregion // Draw/Fill Rectangle
274 |
275 |
276 | #region Draw lines
277 |
278 | ///
279 | /// Draws a line between the specified points using the specified stroke style.
280 | ///
281 | public void DrawLine(float x1, float y1, float x2, float y2, Color c, float strokeWidth = 1)
282 | {
283 | DrawLine(new PointF(x1, y1), new PointF(x2, y2), c, strokeWidth);
284 | }
285 |
286 | ///
287 | /// Draws a line between the specified points using the specified stroke style.
288 | ///
289 | public void DrawLine(PointF p1, PointF p2, Color c, float strokeWidth = 1)
290 | {
291 | var point1 = new D2D_POINT_2F(p1.X, p1.Y);
292 | var point2 = new D2D_POINT_2F(p2.X, p2.Y);
293 | var color = DXHelper.FromColor(c);
294 |
295 | // create solid brush
296 | using var brush = DeviceContext.CreateSolidColorBrush(color);
297 |
298 | // start drawing the line
299 | DeviceContext.DrawLine(point1, point2, brush, strokeWidth);
300 | }
301 |
302 | #endregion // Draw lines
303 |
304 |
305 | #region Draw / Measure text
306 |
307 | ///
308 | /// Draws the specified text using the format information provided.
309 | ///
310 | public void DrawText(string text, string fontFamilyName, float fontSize, float x, float y, Color c, float? textDpi = null, StringAlignment hAlign = StringAlignment.Near, StringAlignment vAlign = StringAlignment.Near, bool isBold = false, bool isItalic = false)
311 | {
312 | var rect = new RectangleF(x, y, int.MaxValue, int.MaxValue);
313 |
314 | DrawText(text, fontFamilyName, fontSize, rect, c, textDpi, hAlign, vAlign, isBold, isItalic);
315 | }
316 |
317 | ///
318 | /// Draws the specified text using the format information provided.
319 | ///
320 | public void DrawText(string text, string fontFamilyName, float fontSize, RectangleF rect, Color c, float? textDpi = null, StringAlignment hAlign = StringAlignment.Near, StringAlignment vAlign = StringAlignment.Near, bool isBold = false, bool isItalic = false)
321 | {
322 | // backup base dpi
323 | DeviceContext.Object.GetDpi(out var baseDpiX, out var baseDpiY);
324 | var region = new D2D_RECT_F(rect.Left, rect.Top, rect.Right, rect.Bottom);
325 |
326 | // fix DPI
327 | if (textDpi != null)
328 | {
329 | var dpiFactor = textDpi.Value / 96.0f;
330 | DeviceContext.Object.SetDpi(textDpi.Value, textDpi.Value);
331 |
332 | fontSize += dpiFactor;
333 |
334 | region.left /= dpiFactor;
335 | region.top /= dpiFactor;
336 | region.right /= dpiFactor;
337 | region.bottom /= dpiFactor;
338 | }
339 |
340 | // format text
341 | var fontWeight = isBold
342 | ? DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_BOLD
343 | : DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL;
344 | var fontStyle = isItalic
345 | ? DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_ITALIC
346 | : DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL;
347 | var horzAlign = hAlign switch
348 | {
349 | StringAlignment.Near => DWRITE_TEXT_ALIGNMENT.DWRITE_TEXT_ALIGNMENT_LEADING,
350 | StringAlignment.Center => DWRITE_TEXT_ALIGNMENT.DWRITE_TEXT_ALIGNMENT_CENTER,
351 | StringAlignment.Far => DWRITE_TEXT_ALIGNMENT.DWRITE_TEXT_ALIGNMENT_TRAILING,
352 | _ => DWRITE_TEXT_ALIGNMENT.DWRITE_TEXT_ALIGNMENT_LEADING,
353 | };
354 | var vertAlign = vAlign switch
355 | {
356 | StringAlignment.Near => DWRITE_PARAGRAPH_ALIGNMENT.DWRITE_PARAGRAPH_ALIGNMENT_NEAR,
357 | StringAlignment.Center => DWRITE_PARAGRAPH_ALIGNMENT.DWRITE_PARAGRAPH_ALIGNMENT_CENTER,
358 | StringAlignment.Far => DWRITE_PARAGRAPH_ALIGNMENT.DWRITE_PARAGRAPH_ALIGNMENT_FAR,
359 | _ => DWRITE_PARAGRAPH_ALIGNMENT.DWRITE_PARAGRAPH_ALIGNMENT_NEAR,
360 | };
361 |
362 | using var format = DWriteFactory.CreateTextFormat(fontFamilyName, fontSize,
363 | weight: fontWeight, style: fontStyle);
364 | format.Object.SetTextAlignment(horzAlign);
365 | format.Object.SetParagraphAlignment(vertAlign);
366 | format.Object.SetWordWrapping(DWRITE_WORD_WRAPPING.DWRITE_WORD_WRAPPING_WRAP);
367 |
368 |
369 | // create solid brush
370 | var color = DXHelper.FromColor(c);
371 | using var brush = DeviceContext.CreateSolidColorBrush(color);
372 |
373 |
374 | // draw text
375 | DeviceContext.DrawText(text, format, region, brush, D2D1_DRAW_TEXT_OPTIONS.D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
376 |
377 | // restore base dpi
378 | if (textDpi != null)
379 | {
380 | DeviceContext.Object.SetDpi(baseDpiX, baseDpiY);
381 | }
382 | }
383 |
384 | ///
385 | /// Measure text.
386 | ///
387 | public SizeF MeasureText(string text, string fontFamilyName, float fontSize, float maxWidth = float.MaxValue, float maxHeight = float.MaxValue, float textDpi = 96, bool isBold = false, bool isItalic = false)
388 | {
389 | var size = new SizeF(maxWidth, maxHeight);
390 |
391 | return MeasureText(text, fontFamilyName, fontSize, size, textDpi, isBold, isItalic);
392 | }
393 |
394 | ///
395 | /// Measure text.
396 | ///
397 | public SizeF MeasureText(string text, string fontFamilyName, float fontSize, SizeF size, float textDpi = 96, bool isBold = false, bool isItalic = false)
398 | {
399 | // fix DPI
400 | var dpiScale = textDpi / 96.0f;
401 | fontSize = (fontSize + dpiScale) * dpiScale;
402 |
403 | // format text
404 | var fontWeight = isBold
405 | ? DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_BOLD
406 | : DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL;
407 | var fontStyle = isItalic
408 | ? DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_ITALIC
409 | : DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL;
410 |
411 | using var format = DWriteFactory.CreateTextFormat(fontFamilyName, fontSize,
412 | weight: fontWeight, style: fontStyle,
413 | stretch: DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL);
414 | format.Object.SetWordWrapping(DWRITE_WORD_WRAPPING.DWRITE_WORD_WRAPPING_WRAP);
415 |
416 | // create layout
417 | using var layout = DWriteFactory.CreateTextLayout(format, text, text.Length, size.Width, size.Height);
418 |
419 | // measure text
420 | layout.Object.GetMetrics(out var metrics);
421 |
422 | return new SizeF(metrics.width, metrics.height);
423 | }
424 |
425 | #endregion // Draw / Measure text
426 |
427 |
428 | #region Draw / Fill Geometry
429 |
430 | ///
431 | /// Get geometry from a combined 2 rectangles.
432 | ///
433 | public IComObject? GetCombinedRectanglesGeometry(RectangleF rect1, RectangleF rect2,
434 | float rect1Radius, float rect2Radius, D2D1_COMBINE_MODE combineMode)
435 | {
436 | // create rounded rectangle 1
437 | var roundedRect1 = new D2D1_ROUNDED_RECT()
438 | {
439 | rect = new D2D_RECT_F(rect1.Left, rect1.Top, rect1.Right, rect1.Bottom),
440 | radiusX = rect1Radius,
441 | radiusY = rect1Radius,
442 | };
443 |
444 | // create rounded rectangle 2
445 | var roundedRect2 = new D2D1_ROUNDED_RECT()
446 | {
447 | rect = new D2D_RECT_F(rect2.Left, rect2.Top, rect2.Right, rect2.Bottom),
448 | radiusX = rect2Radius,
449 | radiusY = rect2Radius,
450 | };
451 |
452 |
453 | // create geometries
454 | var shape1Geo = D2DFactory.CreateRoundedRectangleGeometry(roundedRect1);
455 | var shape22Geo = D2DFactory.CreateRoundedRectangleGeometry(roundedRect2);
456 |
457 | // create path geometry to get the combined shape
458 | var pathGeo = D2DFactory.CreatePathGeometry();
459 | pathGeo.Object.Open(out var pathGeoSink).ThrowOnError();
460 |
461 |
462 | // combine 2 geometry shapes
463 | shape1Geo.Object.CombineWithGeometry(shape22Geo.Object, combineMode, IntPtr.Zero, 0, pathGeoSink).ThrowOnError();
464 |
465 | pathGeoSink.Close();
466 | Marshal.ReleaseComObject(pathGeoSink);
467 | shape1Geo.Dispose();
468 | shape22Geo.Dispose();
469 |
470 | return pathGeo;
471 | }
472 |
473 |
474 | ///
475 | /// Get geometry from a combined 2 ellipses.
476 | ///
477 | public IComObject? GetCombinedEllipsesGeometry(RectangleF rect1, RectangleF rect2, D2D1_COMBINE_MODE combineMode)
478 | {
479 | // create ellipse 1
480 | var ellipse1 = new D2D1_ELLIPSE(rect1.X + rect1.Width / 2, rect1.Y + rect1.Height / 2, rect1.Width / 2, rect1.Height / 2);
481 |
482 | // create ellipse 2
483 | var ellipse2 = new D2D1_ELLIPSE(rect2.X + rect2.Width / 2, rect2.Y + rect2.Height / 2, rect2.Width / 2, rect2.Height / 2);
484 |
485 |
486 | // create geometries
487 | var shape1Geo = D2DFactory.CreateEllipseGeometry(ellipse1);
488 | var shape2Geo = D2DFactory.CreateEllipseGeometry(ellipse2);
489 |
490 | // create path geometry to get the combined shape
491 | var pathGeo = D2DFactory.CreatePathGeometry();
492 | pathGeo.Object.Open(out var pathGeoSink).ThrowOnError();
493 |
494 |
495 | // combine 2 geometry shapes
496 | shape1Geo.Object.CombineWithGeometry(shape2Geo.Object, combineMode, IntPtr.Zero, 0, pathGeoSink).ThrowOnError();
497 |
498 | pathGeoSink.Close();
499 | Marshal.ReleaseComObject(pathGeoSink);
500 | shape1Geo.Dispose();
501 | shape2Geo.Dispose();
502 |
503 |
504 | return pathGeo;
505 | }
506 |
507 |
508 | ///
509 | /// Draw geometry.
510 | ///
511 | public void DrawGeometry(IComObject? geo, Color borderColor, Color? fillColor = null, float strokeWidth = 1f)
512 | {
513 | if (geo is not IComObject geometry) return;
514 |
515 | // draw background color -----------------------------------
516 | if (fillColor != null)
517 | {
518 | var bgColor = DXHelper.FromColor(fillColor.Value);
519 | using var bgBrush = DeviceContext.CreateSolidColorBrush(bgColor);
520 |
521 | // fill the combined geometry
522 | DeviceContext.FillGeometry(geometry, bgBrush);
523 | }
524 |
525 |
526 | // draw border color ----------------------------------------
527 | if (borderColor != Color.Transparent)
528 | {
529 | var bdColor = DXHelper.FromColor(borderColor);
530 | using var borderBrush = DeviceContext.CreateSolidColorBrush(bdColor);
531 |
532 | // draw the combined geometry
533 | DeviceContext.DrawGeometry(geometry, borderBrush, strokeWidth);
534 | }
535 | }
536 |
537 | #endregion // Draw / Fill Geometry
538 |
539 |
540 | #region Others
541 |
542 | ///
543 | /// Executes all pending drawing commands.
544 | ///
545 | public void Flush()
546 | {
547 | DeviceContext.Object.Flush(IntPtr.Zero, IntPtr.Zero);
548 | }
549 |
550 |
551 | ///
552 | /// Clears the drawing area to the specified color.
553 | ///
554 | public void ClearBackground(Color color)
555 | {
556 | var d3Color = DXHelper.FromColor(color);
557 |
558 | DeviceContext.Clear(d3Color);
559 | }
560 |
561 |
562 | #endregion // Others
563 |
564 |
565 | }
566 |
--------------------------------------------------------------------------------
/Source/DXControl/DXHelper.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 | Copyright (C) 2022 - 2025 DUONG DIEU PHAP
4 | Project & license info: https://github.com/d2phap/DXControl
5 | */
6 | using DirectN;
7 | using WicNet;
8 |
9 | namespace D2Phap.DXControl;
10 |
11 | public static class DXHelper
12 | {
13 | ///
14 | /// Disposes the Direct2D1 bitmap.
15 | ///
16 | public static void DisposeD2D1Bitmap(ref IComObject? bmp)
17 | {
18 | if (bmp == null) return;
19 |
20 | Interlocked.Exchange(ref bmp, null)?.Dispose();
21 | }
22 |
23 |
24 | ///
25 | /// Converts to .
26 | ///
27 | public static _D3DCOLORVALUE FromColor(Color color, byte? alpha = null)
28 | {
29 | return _D3DCOLORVALUE.FromArgb(alpha ?? color.A, color.R, color.G, color.B);
30 | }
31 |
32 |
33 | ///
34 | /// Converts to .
35 | ///
36 | public static D2D_RECT_F ToD2DRectF(Rectangle rect)
37 | {
38 | return new D2D_RECT_F(
39 | rect.Left,
40 | rect.Top,
41 | rect.Right,
42 | rect.Bottom);
43 | }
44 |
45 |
46 | ///
47 | /// Converts to .
48 | ///
49 | public static D2D_RECT_F ToD2DRectF(RectangleF rect)
50 | {
51 | return new D2D_RECT_F(
52 | rect.Left,
53 | rect.Top,
54 | rect.Right,
55 | rect.Bottom);
56 | }
57 |
58 |
59 | ///
60 | /// Creates .
61 | ///
62 | public static D2D_RECT_F ToD2DRectF(float x, float y, float width, float height)
63 | {
64 | return new D2D_RECT_F(x, y, x + width, y + height);
65 | }
66 |
67 |
68 | ///
69 | /// Converts to
70 | ///
71 | public static RectangleF ToRectangle(D2D_RECT_F rect)
72 | {
73 | return new RectangleF(rect.left, rect.top, rect.Width, rect.Height);
74 | }
75 |
76 |
77 | ///
78 | /// Converts to
79 | ///
80 | public static SizeF ToSize(D2D_SIZE_F size)
81 | {
82 | return new SizeF(size.width, size.height);
83 | }
84 |
85 |
86 | ///
87 | /// Converts to
88 | ///
89 | public static PointF ToPoint(D2D_POINT_2F point)
90 | {
91 | return new PointF(point.x, point.y);
92 | }
93 |
94 |
95 | ///
96 | /// Creates default .
97 | ///
98 | public static D2D1_BITMAP_PROPERTIES1 CreateDefaultBitmapProps()
99 | {
100 | return new D2D1_BITMAP_PROPERTIES1()
101 | {
102 | pixelFormat = new D2D1_PIXEL_FORMAT()
103 | {
104 | alphaMode = D2D1_ALPHA_MODE.D2D1_ALPHA_MODE_PREMULTIPLIED,
105 | format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
106 | },
107 | dpiX = 96.0f,
108 | dpiY = 96.0f,
109 | };
110 | }
111 |
112 |
113 |
114 | ///
115 | /// Converts to COM object.
116 | ///
117 | public static IComObject? ToD2D1Bitmap(IComObject? dc, WicBitmapSource? wicSrc, D2D1_BITMAP_PROPERTIES1? bitmapProps = null)
118 | {
119 | if (dc == null || wicSrc == null)
120 | {
121 | return null;
122 | }
123 |
124 | wicSrc.ConvertTo(WicPixelFormat.GUID_WICPixelFormat32bppPBGRA);
125 |
126 | // create D2D1Bitmap from WICBitmapSource
127 | bitmapProps ??= new D2D1_BITMAP_PROPERTIES1()
128 | {
129 | pixelFormat = new D2D1_PIXEL_FORMAT()
130 | {
131 | alphaMode = D2D1_ALPHA_MODE.D2D1_ALPHA_MODE_PREMULTIPLIED,
132 | format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
133 | },
134 | dpiX = 96.0f,
135 | dpiY = 96.0f,
136 | };
137 |
138 | var comBmp = dc.CreateBitmapFromWicBitmap(wicSrc.ComObject, bitmapProps);
139 |
140 | return comBmp;
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/Source/DXControl/Enums.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 | Copyright (C) 2022 - 2025 DUONG DIEU PHAP
4 | Project & license info: https://github.com/d2phap/DXControl
5 | */
6 |
7 | namespace D2Phap.DXControl;
8 |
9 |
10 | ///
11 | /// Reason when the device is created.
12 | ///
13 | public enum DeviceCreatedReason
14 | {
15 | FirstTime,
16 | UseHardwareAccelerationChanged,
17 | DeviceLost,
18 | }
19 |
20 |
21 | ///
22 | /// Interpolation mode for .
23 | ///
24 | public enum InterpolationMode
25 | {
26 | NearestNeighbor = 0,
27 | Linear = 1,
28 | Cubic = 2,
29 | SampleLinear = 3,
30 | Antisotropic = 4,
31 | HighQualityBicubic = 5,
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/Source/DXControl/Events.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 | Copyright (C) 2022 - 2025 DUONG DIEU PHAP
4 | Project & license info: https://github.com/d2phap/DXControl
5 | */
6 |
7 | namespace D2Phap.DXControl;
8 |
9 |
10 | ///
11 | /// Provides the data for event.
12 | ///
13 | public class RenderEventArgs(DXGraphics g) : EventArgs
14 | {
15 | ///
16 | /// Gets the object used to draw.
17 | ///
18 | public DXGraphics Graphics { get; init; } = g;
19 | }
20 |
21 |
22 |
23 | ///
24 | /// Provides the data for event.
25 | ///
26 | public class DeviceCreatedEventArgs(DeviceCreatedReason reason) : EventArgs
27 | {
28 | ///
29 | /// Gets reason why the device is created.
30 | ///
31 | public DeviceCreatedReason Reason { get; init; } = reason;
32 | }
33 |
34 |
35 |
36 | ///
37 | /// Provides the data for event.
38 | ///
39 | public class FrameEventArgs : EventArgs
40 | {
41 | #if NET8_0_OR_GREATER
42 |
43 | ///
44 | /// Gets, sets the period for .
45 | ///
46 | public TimeSpan Period { get; set; } = TimeSpan.FromMilliseconds(10);
47 |
48 | #endif
49 |
50 | public FrameEventArgs() { }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Source/Demo/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Source/Demo/Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows;net7.0-windows;net8.0-windows
6 | enable
7 | true
8 | enable
9 | AnyCPU;x64;x86
10 | app.manifest
11 |
12 | PerMonitorV2
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | PreserveNewest
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Source/Demo/DemoCanvas.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 | Copyright (C) 2022 - 2025 DUONG DIEU PHAP
4 | Project & license info: https://github.com/d2phap/DXControl
5 | */
6 | using D2Phap.DXControl;
7 | using DirectN;
8 | using WicNet;
9 |
10 | namespace Demo;
11 |
12 | public class DemoCanvas : DXCanvas
13 | {
14 | private IComObject? _bitmapD2d = null;
15 | private Rectangle rectText = new(40, 40, 300, 200);
16 | private WicBitmapSource? _wicSrc;
17 |
18 |
19 | public WicBitmapSource? Image
20 | {
21 | set
22 | {
23 | _wicSrc = value;
24 | CreateD2DBitmap();
25 | }
26 | }
27 |
28 |
29 | private void CreateD2DBitmap()
30 | {
31 | DXHelper.DisposeD2D1Bitmap(ref _bitmapD2d);
32 |
33 | if (Device == null || _wicSrc == null)
34 | {
35 | _bitmapD2d = null;
36 | return;
37 | }
38 |
39 | // create D2DBitmap from WICBitmapSource
40 | var bitmapProps = DXHelper.CreateDefaultBitmapProps();
41 | _wicSrc.ConvertTo(WicPixelFormat.GUID_WICPixelFormat32bppPBGRA);
42 |
43 | _bitmapD2d = Device.CreateBitmapFromWicBitmap(_wicSrc.ComObject, bitmapProps);
44 | }
45 |
46 |
47 | public DemoCanvas()
48 | {
49 | CheckFPS = true;
50 | }
51 |
52 |
53 | protected override void OnDeviceCreated(DeviceCreatedReason reason)
54 | {
55 | base.OnDeviceCreated(reason);
56 |
57 | if (reason == DeviceCreatedReason.UseHardwareAccelerationChanged)
58 | {
59 | CreateD2DBitmap();
60 | }
61 | }
62 |
63 |
64 | protected override void OnRender(DXGraphics g)
65 | {
66 | var p1 = new Point(0, 0);
67 | var p2 = new Point(ClientSize.Width, ClientSize.Height);
68 |
69 | // draw X
70 | g.DrawLine(p1, p2, Color.Blue, 10.0f);
71 | g.DrawLine(new(ClientSize.Width, 0), new(0, ClientSize.Height), Color.Red, 3.0f);
72 |
73 |
74 | // draw D2DBitmap image
75 | if (_bitmapD2d != null)
76 | {
77 | _bitmapD2d.Object.GetSize(out var size);
78 |
79 | g.DrawBitmap(_bitmapD2d,
80 | destRect: new RectangleF(150, 150, size.width * 3, size.height * 3),
81 | srcRect: new RectangleF(0, 0, size.width, size.height),
82 | interpolation: InterpolationMode.NearestNeighbor
83 | );
84 | }
85 |
86 |
87 | // draw rectangle border
88 | g.DrawRectangle(10f, 10f, ClientSize.Width - 20, ClientSize.Height - 20, 0,
89 | Color.GreenYellow, null, 5f);
90 |
91 |
92 | // draw and fill rounded rectangle
93 | g.DrawRectangle(ClientSize.Width / 1.5f, ClientSize.Height / 1.5f, 300, 100,
94 | 20f, Color.LightCyan, Color.FromArgb(180, Color.Cyan), 3f);
95 |
96 | // draw and fill ellipse
97 | g.DrawEllipse(200, 200, 300, 200, Color.FromArgb(120, Color.Magenta), Color.Purple, 5);
98 |
99 |
100 | // draw geometry D2D only
101 | if (g is DXGraphics dg)
102 | {
103 | using var geo = dg.GetCombinedRectanglesGeometry(new RectangleF(200, 300, 300, 300),
104 | new Rectangle(250, 250, 300, 100), 0, 0, D2D1_COMBINE_MODE.D2D1_COMBINE_MODE_INTERSECT);
105 | dg.DrawGeometry(geo, Color.Transparent, Color.Yellow, 2);
106 |
107 |
108 | using var geo2 = dg.GetCombinedEllipsesGeometry(new Rectangle(450, 450, 300, 100), new RectangleF(400, 400, 300, 300), D2D1_COMBINE_MODE.D2D1_COMBINE_MODE_EXCLUDE);
109 | dg.DrawGeometry(geo2, Color.Transparent, Color.Green, 2f);
110 | }
111 |
112 |
113 | // draw and fill rectangle
114 | g.DrawRectangle(rectText, 0, Color.Green, Color.FromArgb(100, Color.Yellow));
115 |
116 |
117 | // draw text
118 | var text = "Dương\r\nDiệu\r\nPháp\r\n😵🪺🐷😶🌫️🤯🫶🏿";
119 | var textSize = g.MeasureText(text, Font.Name, 12, textDpi: DeviceDpi, isBold: true, isItalic: true);
120 | g.DrawText($"{text}\r\n{textSize}", Font.Name, 12, rectText,
121 | Color.Red, DeviceDpi, StringAlignment.Near, isBold: true, isItalic: true);
122 | g.DrawRectangle(new RectangleF(rectText.Location, textSize), 0, Color.Red);
123 |
124 |
125 | // draw FPS info
126 | var engine = UseHardwareAcceleration ? "Hardware" : "Software";
127 | g.DrawText($"FPS: {FPS} - {engine}", Font.Name, 18, 0, 0, Color.Purple, DeviceDpi);
128 |
129 | }
130 |
131 |
132 | protected override void OnFrame(FrameEventArgs e)
133 | {
134 | base.OnFrame(e);
135 | rectText.Width++;
136 | }
137 |
138 | }
--------------------------------------------------------------------------------
/Source/Demo/Form1.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace Demo
2 | {
3 | partial class Form1
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | canvas = new DemoCanvas();
32 | chkD2D = new CheckBox();
33 | chkAnimation = new CheckBox();
34 | SuspendLayout();
35 | //
36 | // canvas
37 | //
38 | canvas.AllowDrop = true;
39 | canvas.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
40 | canvas.BackColor = Color.FromArgb(128, 128, 255);
41 | canvas.BaseDpi = 96F;
42 | canvas.CheckFPS = true;
43 | canvas.EnableAnimation = true;
44 | canvas.Location = new Point(20, 90);
45 | canvas.Margin = new Padding(6, 5, 6, 5);
46 | canvas.Name = "canvas";
47 | canvas.RequestUpdateFrame = true;
48 | canvas.Size = new Size(2114, 1293);
49 | canvas.TabIndex = 0;
50 | canvas.Text = "dxCanvas1";
51 | canvas.DragDrop += canvas_DragDrop;
52 | canvas.DragOver += canvas_DragOver;
53 | //
54 | // chkD2D
55 | //
56 | chkD2D.AutoSize = true;
57 | chkD2D.Checked = true;
58 | chkD2D.CheckState = CheckState.Checked;
59 | chkD2D.Location = new Point(20, 25);
60 | chkD2D.Margin = new Padding(6, 5, 6, 5);
61 | chkD2D.Name = "chkD2D";
62 | chkD2D.Size = new Size(372, 49);
63 | chkD2D.TabIndex = 1;
64 | chkD2D.Text = "Use Hardware acceleration";
65 | chkD2D.UseVisualStyleBackColor = true;
66 | chkD2D.CheckedChanged += chkD2D_CheckedChanged;
67 | //
68 | // chkAnimation
69 | //
70 | chkAnimation.AutoSize = true;
71 | chkAnimation.Checked = true;
72 | chkAnimation.CheckState = CheckState.Checked;
73 | chkAnimation.Location = new Point(484, 25);
74 | chkAnimation.Margin = new Padding(6, 5, 6, 5);
75 | chkAnimation.Name = "chkAnimation";
76 | chkAnimation.Size = new Size(303, 49);
77 | chkAnimation.TabIndex = 2;
78 | chkAnimation.Text = "Enable animation";
79 | chkAnimation.UseVisualStyleBackColor = true;
80 | chkAnimation.CheckedChanged += ChkAnimation_CheckedChanged;
81 | //
82 | // Form1
83 | //
84 | AutoScaleDimensions = new SizeF(18F, 45F);
85 | AutoScaleMode = AutoScaleMode.Font;
86 | ClientSize = new Size(2154, 1403);
87 | Controls.Add(chkAnimation);
88 | Controls.Add(chkD2D);
89 | Controls.Add(canvas);
90 | Margin = new Padding(6, 5, 6, 5);
91 | Name = "Form1";
92 | Padding = new Padding(20);
93 | Text = "D2Phap.DXControl demo";
94 | Load += Form1_Load;
95 | ResumeLayout(false);
96 | PerformLayout();
97 | }
98 |
99 | #endregion
100 |
101 | private DemoCanvas canvas;
102 | private CheckBox chkD2D;
103 | private CheckBox chkAnimation;
104 | }
105 | }
--------------------------------------------------------------------------------
/Source/Demo/Form1.cs:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 | Copyright (C) 2022-2025 DUONG DIEU PHAP
4 | Project & license info: https://github.com/d2phap/DXControl
5 | */
6 | using ImageMagick;
7 | using Microsoft.Win32.SafeHandles;
8 | using System.Reflection;
9 | using System.Runtime.InteropServices;
10 | using System.Windows.Media.Imaging;
11 | using WicNet;
12 |
13 | namespace Demo;
14 |
15 | public partial class Form1 : Form
16 | {
17 | public Form1()
18 | {
19 | InitializeComponent();
20 | }
21 |
22 | private void Form1_Load(object sender, EventArgs e)
23 | {
24 | var filePath = @"photo.png";
25 |
26 | if (!string.IsNullOrEmpty(filePath))
27 | {
28 | using var imgM = new MagickImage(filePath);
29 |
30 | canvas.UseHardwareAcceleration = imgM.Width <= 16384 && imgM.Height <= 16384;
31 | canvas.Image = FromBitmapSource(imgM.ToBitmapSource());
32 | }
33 | }
34 |
35 |
36 | protected override void OnDpiChanged(DpiChangedEventArgs e)
37 | {
38 | base.OnDpiChanged(e);
39 | }
40 |
41 | public static WicBitmapSource? FromBitmapSource(BitmapSource bmp)
42 | {
43 | if (bmp == null)
44 | return null;
45 |
46 |
47 | var prop = bmp.GetType().GetProperty("WicSourceHandle",
48 | BindingFlags.NonPublic | BindingFlags.Instance);
49 |
50 | var srcHandle = (SafeHandleZeroOrMinusOneIsInvalid?)prop?.GetValue(bmp);
51 | if (srcHandle == null) return null;
52 |
53 |
54 | var obj = Marshal.GetObjectForIUnknown(srcHandle.DangerousGetHandle());
55 | var wicSrc = new WicBitmapSource(obj);
56 | wicSrc.ConvertTo(WicPixelFormat.GUID_WICPixelFormat32bppPBGRA);
57 |
58 | return wicSrc;
59 | }
60 |
61 | private void canvas_DragDrop(object sender, DragEventArgs e)
62 | {
63 | // Drag file from DESKTOP to APP
64 | if (e.Data is null || !e.Data.GetDataPresent(DataFormats.FileDrop))
65 | return;
66 |
67 | var filePaths = (string[])e.Data.GetData(DataFormats.FileDrop, false);
68 |
69 | if (filePaths.Length > 0)
70 | {
71 | Text = filePaths[0];
72 |
73 | using var imgM = new MagickImage(Text);
74 | canvas.Image = FromBitmapSource(imgM.ToBitmapSource());
75 | }
76 | }
77 |
78 | private void canvas_DragOver(object sender, DragEventArgs e)
79 | {
80 | e.Effect = DragDropEffects.Copy;
81 | }
82 |
83 | private void chkD2D_CheckedChanged(object sender, EventArgs e)
84 | {
85 | canvas.UseHardwareAcceleration = chkD2D.Checked;
86 | }
87 |
88 | private void ChkAnimation_CheckedChanged(object sender, EventArgs e)
89 | {
90 | canvas.EnableAnimation = chkAnimation.Checked;
91 | }
92 | }
--------------------------------------------------------------------------------
/Source/Demo/Form1.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 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/Source/Demo/Program.cs:
--------------------------------------------------------------------------------
1 | namespace Demo
2 | {
3 | internal static class Program
4 | {
5 | ///
6 | /// The main entry point for the application.
7 | ///
8 | [STAThread]
9 | static void Main()
10 | {
11 | // To customize application configuration such as set high DPI settings or default font,
12 | // see https://aka.ms/applicationconfiguration.
13 | ApplicationConfiguration.Initialize();
14 | Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
15 | Application.Run(new Form1());
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/Source/Demo/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Demo": {
4 | "commandName": "Project",
5 | "commandLineArgs": "\"C:\\Users\\d2pha\\Desktop\\400.png\""
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/Source/Demo/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
54 |
55 |
56 |
57 |
58 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/Source/Demo/photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d2phap/DXControl/0a2567cf2094c1ed534b876c25918e3a17b4accd/Source/Demo/photo.png
--------------------------------------------------------------------------------