├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── .gitignore ├── Clowd.Clipboard.sln ├── Clowd.Clipboard.snk ├── Clowd_200.png ├── LICENSE ├── README.md ├── src ├── Clowd.Clipboard.Avalonia │ ├── ClipboardHandleAvalonia.cs │ └── Clowd.Clipboard.Avalonia.csproj ├── Clowd.Clipboard.Gdi │ ├── Bitmaps │ │ └── BitmapGdi.cs │ ├── ClipboardHandleGdi.cs │ ├── Clowd.Clipboard.Gdi.csproj │ └── Formats │ │ ├── BytesToGdiBitmapConverter.cs │ │ ├── DibToGdiBitmapConverter.cs │ │ └── GdiHandleToGdiBitmapConverter.cs ├── Clowd.Clipboard.Wpf │ ├── Bitmaps │ │ ├── BitmapWpf.cs │ │ └── BitmapWpfInternal.cs │ ├── ClipboardHandleWpf.cs │ ├── Clowd.Clipboard.Wpf.csproj │ └── Formats │ │ ├── BytesToWicBitmapConverter.cs │ │ ├── DibToWicBitmapConverter.cs │ │ └── GdiHandleToWicBitmapConverter.cs ├── Clowd.Clipboard │ ├── Bitmaps │ │ ├── BitmapCore.cs │ │ ├── BitmapCorePixelFormat.cs │ │ ├── BitmapCorePixelReader.cs │ │ ├── CCITTHuffmanG31D.cs │ │ ├── IBitmapConverter.cs │ │ ├── PointerStream.cs │ │ ├── StructUtil.cs │ │ ├── Structs.cs │ │ └── mscms.cs │ ├── ClipboardBusyException.cs │ ├── ClipboardFormat.cs │ ├── ClipboardHandle.cs │ ├── ClipboardHandlePlatformBase.cs │ ├── ClipboardStaticBase.cs │ ├── ClipboardWindow.cs │ ├── Clowd.Clipboard.csproj │ ├── Formats │ │ ├── FileDropConverter.cs │ │ ├── IDataConverter.cs │ │ ├── Int32Converter.cs │ │ ├── LocaleConverter.cs │ │ └── TextEncodingConverter.cs │ ├── Globals.cs │ ├── NativeMethods.cs │ └── NativeStructs.cs └── Directory.Build.props ├── tests ├── ClipboardAvaloniaTest │ ├── App.axaml │ ├── App.axaml.cs │ ├── ClipboardAvaloniaTest.csproj │ ├── MainWindow.axaml │ ├── MainWindow.axaml.cs │ └── Program.cs ├── ClipboardConsoleTests │ ├── ClipboardConsoleTests.csproj │ ├── Program.cs │ └── bitmapreadtest.png ├── ClipboardUnitTests │ ├── BitmapTests.cs │ ├── ClipboardUnitTests.csproj │ ├── StringTests.cs │ ├── bitmapreadtest.png │ ├── bmp │ │ ├── pal1.bmp │ │ ├── pal1bg.bmp │ │ ├── pal4.bmp │ │ ├── pal8.bmp │ │ ├── pal8v5.bmp │ │ ├── pal8w124.bmp │ │ ├── pal8w125.bmp │ │ ├── pal8w126.bmp │ │ ├── rgb16-565.bmp │ │ ├── rgb16-565pal.bmp │ │ ├── rgb16.bmp │ │ ├── rgb16bfdef.bmp │ │ ├── rgb24.bmp │ │ ├── rgb24pal.bmp │ │ ├── rgb32-7187.bmp │ │ ├── rgb32.bmp │ │ ├── rgb32bf.bmp │ │ ├── rgb32bfdef.bmp │ │ ├── rgba32-1.bmp │ │ └── rgba32-2.bmp │ └── utf8.txt ├── ClipboardWpfTests │ ├── App.xaml │ ├── App.xaml.cs │ ├── ClipboardWpfTests.csproj │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Properties │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ ├── app.manifest │ └── rgba32.png └── Clowd.Bitmaps.BitmapTests │ ├── App.config │ ├── Clowd.Bitmaps.BitmapTests.csproj │ ├── Program.cs │ ├── bitmaps │ ├── pal1.bmp │ ├── pal1bg.bmp │ ├── pal1huff.bmp │ ├── pal1p1.bmp │ ├── pal1wb.bmp │ ├── pal2.bmp │ ├── pal2color.bmp │ ├── pal4.bmp │ ├── pal4gs.bmp │ ├── pal4rle.bmp │ ├── pal4rlecut.bmp │ ├── pal4rletrns.bmp │ ├── pal8-0.bmp │ ├── pal8.bmp │ ├── pal8gs.bmp │ ├── pal8nonsquare.bmp │ ├── pal8offs.bmp │ ├── pal8os2-hs.bmp │ ├── pal8os2-sz.bmp │ ├── pal8os2.bmp │ ├── pal8os2sp.bmp │ ├── pal8os2v2-16.bmp │ ├── pal8os2v2-40sz.bmp │ ├── pal8os2v2-sz.bmp │ ├── pal8os2v2.bmp │ ├── pal8oversizepal.bmp │ ├── pal8rle.bmp │ ├── pal8rlecut.bmp │ ├── pal8rletrns.bmp │ ├── pal8topdown.bmp │ ├── pal8v4.bmp │ ├── pal8v5.bmp │ ├── pal8w124.bmp │ ├── pal8w125.bmp │ ├── pal8w126.bmp │ ├── rgb16-231.bmp │ ├── rgb16-3103.bmp │ ├── rgb16-565.bmp │ ├── rgb16-565pal.bmp │ ├── rgb16.bmp │ ├── rgb16bfdef.bmp │ ├── rgb16faketrns.bmp │ ├── rgb24.bmp │ ├── rgb24jpeg.bmp │ ├── rgb24largepal.bmp │ ├── rgb24lprof.bmp │ ├── rgb24pal.bmp │ ├── rgb24png.bmp │ ├── rgb24prof.bmp │ ├── rgb24prof2.bmp │ ├── rgb24rle24.bmp │ ├── rgb32-111110.bmp │ ├── rgb32-7187.bmp │ ├── rgb32-xbgr.bmp │ ├── rgb32.bmp │ ├── rgb32bf.bmp │ ├── rgb32bfdef.bmp │ ├── rgb32fakealpha.bmp │ ├── rgb32h52.bmp │ ├── rgba16-1924.bmp │ ├── rgba16-4444.bmp │ ├── rgba16-5551.bmp │ ├── rgba32-1.bmp │ ├── rgba32-1010102.bmp │ ├── rgba32-2.bmp │ ├── rgba32-61754.bmp │ ├── rgba32-81284.bmp │ ├── rgba32abf.bmp │ └── rgba32h56.bmp │ ├── clipboard │ ├── clip-adobexd-dibv5.bmp │ ├── clip-chrome-desired.bmp │ ├── clip-chrome-dibv5.bmp │ ├── clip-discord-dibv5.bmp │ ├── clip-firefox-desired.bmp │ ├── clip-firefox-dib.bmp │ ├── clip-firefox-dibv5.bmp │ ├── clip-msexcel-dibv5.bmp │ ├── clip-msword-dibv5.bmp │ ├── clip-paintdotnet-dib.bmp │ ├── clip-paintdotnet-dibv5.bmp │ ├── clip-photoshop-dibv5.bmp │ └── clip-skype-dibv5.bmp │ ├── known_bad │ ├── ba-bm.bmp │ ├── badbitcount.bmp │ ├── badbitssize.bmp │ ├── baddens1.bmp │ ├── baddens2.bmp │ ├── badfilesize.bmp │ ├── badheadersize.bmp │ ├── badpalettesize.bmp │ ├── badplanes.bmp │ ├── badrle.bmp │ ├── badrle4.bmp │ ├── badrle4bis.bmp │ ├── badrle4ter.bmp │ ├── badrlebis.bmp │ ├── badrleter.bmp │ ├── badwidth.bmp │ ├── pal8badindex.bmp │ ├── reallybig.bmp │ ├── rgb16-880.bmp │ ├── rletopdown.bmp │ └── shortfile.bmp │ ├── lcms2-x64.dll │ └── lcms2-x86.dll └── version.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: caesay # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [master, develop] 6 | pull_request: 7 | branches: [master, develop] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - name: Setup dotnet 19 | uses: actions/setup-dotnet@v4 20 | with: 21 | dotnet-version: | 22 | 6.0.x 23 | 8.0.x 24 | 9.0.x 25 | - name: Build 26 | run: dotnet build -c Release 27 | - name: Upload Artifacts 28 | uses: actions/upload-artifact@v4 29 | with: 30 | name: packages 31 | path: ./build/Release/*.nupkg 32 | - name: Publish to NuGet 33 | if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' 34 | run: dotnet nuget push ./build/Release/*.nupkg -k ${{ secrets.NUGET_TOKEN }} -s https://api.nuget.org/v3/index.json --skip-duplicate 35 | -------------------------------------------------------------------------------- /.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 | .idea/ 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Ww][Ii][Nn]32/ 28 | [Aa][Rr][Mm]/ 29 | [Aa][Rr][Mm]64/ 30 | bld/ 31 | [Bb]in/ 32 | [Oo]bj/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | -------------------------------------------------------------------------------- /Clowd.Clipboard.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31912.275 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clowd.Bitmaps.BitmapTests", "tests\Clowd.Bitmaps.BitmapTests\Clowd.Bitmaps.BitmapTests.csproj", "{BB566490-89AC-4710-B6E1-F37E01B11A9D}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FE8AE6E1-D42C-499A-9C8E-7E279C40B01A}" 9 | ProjectSection(SolutionItems) = preProject 10 | .github\workflows\build.yml = .github\workflows\build.yml 11 | src\Directory.Build.props = src\Directory.Build.props 12 | version.json = version.json 13 | EndProjectSection 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C8A1E9B5-1453-4059-85A8-6BFBD12414F1}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clowd.Clipboard", "src\Clowd.Clipboard\Clowd.Clipboard.csproj", "{8BFEF7F4-D577-467A-B0DD-F2DA28760013}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipboardConsoleTests", "tests\ClipboardConsoleTests\ClipboardConsoleTests.csproj", "{65FAC6E7-1DB3-4BAD-BF99-FDA7FA551C09}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipboardUnitTests", "tests\ClipboardUnitTests\ClipboardUnitTests.csproj", "{CEE3026C-62F7-4A9B-B2CC-5B2D620431F2}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipboardWpfTests", "tests\ClipboardWpfTests\ClipboardWpfTests.csproj", "{B5BF5633-C2EE-410D-9883-87FFFEBDD302}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clowd.Clipboard.Wpf", "src\Clowd.Clipboard.Wpf\Clowd.Clipboard.Wpf.csproj", "{19225BE1-B7FE-4A95-B3B5-FF819500CA08}" 26 | EndProject 27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clowd.Clipboard.Gdi", "src\Clowd.Clipboard.Gdi\Clowd.Clipboard.Gdi.csproj", "{68848B44-07F5-4E91-ADE3-1B83B566F616}" 28 | EndProject 29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClipboardAvaloniaTest", "tests\ClipboardAvaloniaTest\ClipboardAvaloniaTest.csproj", "{16EEDB48-1309-44DC-8C86-AD52BCF67EE3}" 30 | EndProject 31 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clowd.Clipboard.Avalonia", "src\Clowd.Clipboard.Avalonia\Clowd.Clipboard.Avalonia.csproj", "{9C00FB18-5599-4C8D-8093-C04F04738A7E}" 32 | EndProject 33 | Global 34 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 35 | Debug|Any CPU = Debug|Any CPU 36 | Release|Any CPU = Release|Any CPU 37 | EndGlobalSection 38 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 39 | {BB566490-89AC-4710-B6E1-F37E01B11A9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {BB566490-89AC-4710-B6E1-F37E01B11A9D}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {BB566490-89AC-4710-B6E1-F37E01B11A9D}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {BB566490-89AC-4710-B6E1-F37E01B11A9D}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {8BFEF7F4-D577-467A-B0DD-F2DA28760013}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {8BFEF7F4-D577-467A-B0DD-F2DA28760013}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {8BFEF7F4-D577-467A-B0DD-F2DA28760013}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {8BFEF7F4-D577-467A-B0DD-F2DA28760013}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {65FAC6E7-1DB3-4BAD-BF99-FDA7FA551C09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {65FAC6E7-1DB3-4BAD-BF99-FDA7FA551C09}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {65FAC6E7-1DB3-4BAD-BF99-FDA7FA551C09}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {65FAC6E7-1DB3-4BAD-BF99-FDA7FA551C09}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {CEE3026C-62F7-4A9B-B2CC-5B2D620431F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {CEE3026C-62F7-4A9B-B2CC-5B2D620431F2}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {CEE3026C-62F7-4A9B-B2CC-5B2D620431F2}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {CEE3026C-62F7-4A9B-B2CC-5B2D620431F2}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {B5BF5633-C2EE-410D-9883-87FFFEBDD302}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {B5BF5633-C2EE-410D-9883-87FFFEBDD302}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {B5BF5633-C2EE-410D-9883-87FFFEBDD302}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {B5BF5633-C2EE-410D-9883-87FFFEBDD302}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {19225BE1-B7FE-4A95-B3B5-FF819500CA08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {19225BE1-B7FE-4A95-B3B5-FF819500CA08}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {19225BE1-B7FE-4A95-B3B5-FF819500CA08}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {19225BE1-B7FE-4A95-B3B5-FF819500CA08}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {68848B44-07F5-4E91-ADE3-1B83B566F616}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {68848B44-07F5-4E91-ADE3-1B83B566F616}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {68848B44-07F5-4E91-ADE3-1B83B566F616}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {68848B44-07F5-4E91-ADE3-1B83B566F616}.Release|Any CPU.Build.0 = Release|Any CPU 67 | {16EEDB48-1309-44DC-8C86-AD52BCF67EE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {16EEDB48-1309-44DC-8C86-AD52BCF67EE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {16EEDB48-1309-44DC-8C86-AD52BCF67EE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {16EEDB48-1309-44DC-8C86-AD52BCF67EE3}.Release|Any CPU.Build.0 = Release|Any CPU 71 | {9C00FB18-5599-4C8D-8093-C04F04738A7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 72 | {9C00FB18-5599-4C8D-8093-C04F04738A7E}.Debug|Any CPU.Build.0 = Debug|Any CPU 73 | {9C00FB18-5599-4C8D-8093-C04F04738A7E}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {9C00FB18-5599-4C8D-8093-C04F04738A7E}.Release|Any CPU.Build.0 = Release|Any CPU 75 | EndGlobalSection 76 | GlobalSection(SolutionProperties) = preSolution 77 | HideSolutionNode = FALSE 78 | EndGlobalSection 79 | GlobalSection(NestedProjects) = preSolution 80 | {BB566490-89AC-4710-B6E1-F37E01B11A9D} = {C8A1E9B5-1453-4059-85A8-6BFBD12414F1} 81 | {65FAC6E7-1DB3-4BAD-BF99-FDA7FA551C09} = {C8A1E9B5-1453-4059-85A8-6BFBD12414F1} 82 | {CEE3026C-62F7-4A9B-B2CC-5B2D620431F2} = {C8A1E9B5-1453-4059-85A8-6BFBD12414F1} 83 | {B5BF5633-C2EE-410D-9883-87FFFEBDD302} = {C8A1E9B5-1453-4059-85A8-6BFBD12414F1} 84 | {16EEDB48-1309-44DC-8C86-AD52BCF67EE3} = {C8A1E9B5-1453-4059-85A8-6BFBD12414F1} 85 | EndGlobalSection 86 | GlobalSection(ExtensibilityGlobals) = postSolution 87 | SolutionGuid = {17CED604-57F9-4E82-943E-CC629E409A23} 88 | EndGlobalSection 89 | EndGlobal 90 | -------------------------------------------------------------------------------- /Clowd.Clipboard.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/Clowd.Clipboard.snk -------------------------------------------------------------------------------- /Clowd_200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/Clowd_200.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Caelan Sayler 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Clowd.Clipboard 4 | This library is a light-weight clipboard replacement library for dotnet. It is not tightly coupled to a UI framework, and it contains a [custom bitmap parser](#clowdclipboardbitmaps) to help deal with some of the inconsistencies which are present in clipboard images. 5 | 6 | Join us on Discord: [![Discord](https://img.shields.io/discord/767856501477343282?style=flat-square&color=purple)](https://discord.gg/M6he8ZPAAJ) 7 | 8 | 9 | 10 | | **Package** | **Nuget** | **Notes** | 11 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 12 | | [Clowd.Clipboard](https://www.nuget.org/packages/Clowd.Clipboard) | [![Nuget](https://img.shields.io/nuget/v/Clowd.Clipboard?style=flat-square)](https://www.nuget.org/packages/Clowd.Clipboard/) | Core library containing basic clipboard functionality. No image/bitmap support. | 13 | | [Clowd.Clipboard.Gdi](https://www.nuget.org/packages/Clowd.Clipboard.Gdi) | [![Nuget](https://img.shields.io/nuget/v/Clowd.Clipboard.Gdi?style=flat-square)](https://www.nuget.org/packages/Clowd.Clipboard.Gdi/) | Adds `ClipboardGdi`, images using `System.Drawing.Bitmap`. | 14 | | [Clowd.Clipboard.Wpf](https://www.nuget.org/packages/Clowd.Clipboard.Wpf) | [![Nuget](https://img.shields.io/nuget/v/Clowd.Clipboard.Wpf?style=flat-square)](https://www.nuget.org/packages/Clowd.Clipboard.Wpf/) | Adds `ClipboardWpf`, images using `System.Windows.Media.Imaging.BitmapSource`. | 15 | | [Clowd.Clipboard.Avalonia](https://www.nuget.org/packages/Clowd.Clipboard.Avalonia) | [![Nuget](https://img.shields.io/nuget/v/Clowd.Clipboard.Avalonia?style=flat-square)](https://www.nuget.org/packages/Clowd.Clipboard.Avalonia/) | Adds `ClipboardAvalonia`, images using `Avalonia.Media.Imaging.Bitmap`. | 16 | 17 | 18 | 19 | ## Clipboard Examples 20 | 21 | Below are several examples of how to do clipboard operations. 22 | 23 | 24 | 25 | ### Getting or setting simple types (single operation) 26 | 27 | The static helper (eg. `ClipboardWpf` or `ClipboardGdi`) has methods for simple things. This takes care of opening the clipboard, reading the data, and then disposing the handle. 28 | 29 | ```cs 30 | // get text on clipboard 31 | string clipboardText = await ClipboardWpf.GetTextAsync(); 32 | 33 | // get image on clipboard 34 | BitmapSource clipboardImg = await ClipboardWpf.GetImageAsync(); 35 | 36 | // set image on clipboard (this clears what was previously on clipboard) 37 | ClipboardWpf.SetImage(clipboardImg); 38 | ``` 39 | 40 | 41 | 42 | ### Getting or setting complex types (multi operation) 43 | 44 | You should not use the static helper methods if you need to perform several clipboard operations. If you need to read or set multiple formats at once, or check for what formats are available, you should instead open a clipboard handle and dispose it when you're done. 45 | 46 | ```cs 47 | BitmapSource clipImage; 48 | string clipText; 49 | 50 | using (var handle = await ClipboardWpf.OpenAsync()); 51 | { 52 | if (handle.ContainsText()) 53 | { 54 | clipText = handle.GetText(); 55 | } 56 | 57 | if (handle.ContainsImage()) 58 | { 59 | clipImage = handle.GetImage(); 60 | } 61 | } 62 | ``` 63 | 64 | 65 | 66 | ### Reading list of currently available clipboard formats 67 | 68 | ```cs 69 | using (var handle = await ClipboardWpf.OpenAsync()); 70 | { 71 | Console.WriteLine("Formats currently on the clipboard: "); 72 | foreach (var f in handle.GetPresentFormats()) 73 | Console.WriteLine(" - " + f.Name); 74 | } 75 | ``` 76 | 77 | 78 | 79 | ### Using custom clipboard formats 80 | 81 | Using a custom/application specific format is very easy with this library. You should first register your format somewhere statically in your application. 82 | 83 | ```cs 84 | // a custom format stored on the clipboard as UTF-8. 85 | // the name chosen for the format should be globally unique, so consider 86 | // using a guid if you do not intend to share with other applications. 87 | private static readonly ClipboardFormat myCustomTextFormat 88 | = ClipboardFormat.CreateCustomFormat("MyGloballyUniqueFormatId", new TextUtf8()); 89 | 90 | // a custom format for storing binary data 91 | private static readonly ClipboardFormat myCustomBinaryFormat 92 | = ClipboardFormat.CreateCustomFormat("MyGloballyUniqueFormatId_2"); 93 | ``` 94 | 95 | Once your clipboard format is registered, you can use it the same way as any built-in format. 96 | 97 | ```csharp 98 | // set custom data to clipboard 99 | using (var handle = await ClipboardWpf.OpenAsync()) 100 | { 101 | // it is possible to set multiple items to the clipboard. 102 | handle.SetFormat(myCustomTextFormat, "My Custom Text"); 103 | handle.SetFormat(myCustomBinaryFormat, new byte[0]); 104 | 105 | // this is a common "built-in" specific format not covered by the simple functions such as "GetText". 106 | handle.SetFormat(ClipboardFormat.Html, "Some Html to share with other applications"); 107 | } 108 | 109 | // later, read custom data from clipboard 110 | using (var handle = await ClipboardWpf.OpenAsync()) 111 | { 112 | if (handle.ContainsFormat(myCustomTextFormat)) 113 | { 114 | string myCustomText = handle.GetFormatType(myCustomTextFormat); 115 | } 116 | if (handle.ContainsFormat(myCustomBinaryFormat)) 117 | { 118 | byte[] myCustomBytes = handle.GetFormatBytes(myCustomBinaryFormat); 119 | } 120 | } 121 | ``` 122 | 123 | **Note:** you can use `CreateCustomFormat(string formatName)` to read data added to the clipboard by other windows applications. You can provide your own `IDataConverter` if you wish to automatically translate this to a dotnet type using the `GetFormatType` method, or you can retrieve the data as a `byte[]` with the `GetFormatBytes` method. 124 | 125 | 126 | 127 | # Clowd.Clipboard.Bitmaps 128 | 129 | The purpose of this library is to attempt to create a more robust BMP/DIB parser than currently exists in WPF or in System.Drawing (GDI+). It's purpose is to provide good interop with packed dibs on the Windows Clipboard, which WPF and GDI do not support. 130 | 131 | There are two big barriers to reading Bitmap's on the clipboard; Both `CF_DIB` and `CF_DIBV5` are available, but they lack `BITMAPFILEHEADER`'s. Additionally, WPF and GDI do not support the `BITMAPV5HEADER` present in `CF_DIBV5` and therefore can not properly read transparency data. This library has been tested with a variety of weird, rare, invalid bitmap files and supports every bitmap reference file I could come across - including rare reader formats (like OS2v1, OS2v2) and rare compression algorithms like RLE and Huffman1D. 132 | 133 | This library may play better with bitmaps written to the clipboard by native applications than GDI or WPF can manage. 134 | 135 | ### Bitmap Known Limitations / Issues 136 | - Bitmap data will be translated to sRGB if calibration data and/or a embedded ICC color profile exists in the bitmap file. This was done by design, because color profile support in WPF and GDI is limited. This library does not support writing a color profile. 137 | - Not all pixel formats are supported natively, some are converted to Bgra32 when parsing 138 | - Does not support Gray colorspace bitmaps 139 | 140 | ### Bitmap Example 141 | There is a convenient to use wrapper for both GDI and WPF, depending on your choice of UI technology. 142 | ```cs 143 | byte[] imageBytes = File.ReadAllBytes("file.bmp"); 144 | BitmapSource bitmap = BitmapWpf.FromBytes(imageBytes, BitmapWpfReaderFlags.PreserveInvalidAlphaChannel); 145 | 146 | // profit! 147 | byte[] imageBytes2 = BitmapWpf.ToBytes(bitmap); 148 | ``` 149 | 150 | ### Bitmap Compatibility 151 | 152 | - :heavy_check_mark: Files with or without a `BITMAPFILEHEADER` 153 | - :heavy_check_mark: Files with any known BMP header format, including WindowsV1, V2, V3, V4, V5, OS2v1, OS2v2 154 | - :heavy_check_mark: Files with logical color space / calibration data 155 | - :heavy_check_mark: Files with embedded ICC color formats 156 | - :heavy_check_mark: Files with any valid compression format, including RGB, BITFIELDS, ALPHABITFIELDS, JPEG, PNG, HUFFMAN1D (G31D), RLE4/8/24 157 | - :heavy_check_mark: Files with completely non-standard pixel layout (for example, 32bpp with the following layout: 7B-25G-0R-0A) 158 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Avalonia/ClipboardHandleAvalonia.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | using AvaBitmap = Avalonia.Media.Imaging.Bitmap; 4 | 5 | namespace Clowd.Clipboard; 6 | 7 | /// 8 | /// Provides static methods for easy access to some of the most basic functionality of . 9 | /// 10 | [SupportedOSPlatform("windows")] 11 | public class ClipboardAvalonia : ClipboardStaticBase 12 | { 13 | private ClipboardAvalonia() { } 14 | } 15 | 16 | /// 17 | [SupportedOSPlatform("windows")] 18 | public class ClipboardHandleAvalonia : ClipboardHandleGdiBase, IClipboardHandlePlatform 19 | { 20 | /// 21 | public virtual AvaBitmap GetImage() 22 | { 23 | using var gdi = GetImageImpl(); 24 | 25 | if (gdi == null) 26 | return null; 27 | 28 | var bitmapData = gdi.LockBits( 29 | new Rectangle(0, 0, gdi.Width, gdi.Height), 30 | ImageLockMode.ReadOnly, 31 | PixelFormat.Format32bppPArgb); 32 | 33 | var bmp = new AvaBitmap( 34 | Avalonia.Platform.PixelFormat.Bgra8888, 35 | Avalonia.Platform.AlphaFormat.Premul, 36 | bitmapData.Scan0, 37 | new Avalonia.PixelSize(bitmapData.Width, bitmapData.Height), 38 | new Avalonia.Vector(gdi.HorizontalResolution, gdi.VerticalResolution), 39 | bitmapData.Stride); 40 | 41 | gdi.UnlockBits(bitmapData); 42 | 43 | return bmp; 44 | } 45 | 46 | /// 47 | public virtual void SetImage(AvaBitmap bitmap) 48 | { 49 | using var ms = new MemoryStream(); 50 | bitmap.Save(ms); 51 | 52 | using var gdi = new Bitmap(ms); 53 | 54 | SetImageImpl(gdi); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Avalonia/Clowd.Clipboard.Avalonia.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461;netstandard2.0;net6.0;net8.0 5 | true 6 | true 7 | Clowd.Clipboard 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Gdi/Bitmaps/BitmapGdi.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | 4 | namespace Clowd.Clipboard.Bitmaps; 5 | 6 | /// 7 | /// Provides a GDI+ implementation of Bitmap reader and writer. This bitmap library can read almost any kind of bitmap and 8 | /// tries to do a better job than Gdi+ does in terms of coverage and it also tries to handle some nuances of how other native 9 | /// applications write bitmaps especially when reading from or writing to the clipboard. 10 | /// 11 | [SupportedOSPlatform("windows")] 12 | public unsafe class BitmapGdi : BitmapConverterStaticBase 13 | { 14 | /// 15 | public override byte[] GetBytes(Bitmap bitmap, BitmapWriterFlags wFlags) 16 | { 17 | // default - this will cause GDI to convert the pixel format to bgra32 if we don't know the format directly 18 | var gdiFmt = PixelFormat.Format32bppArgb; 19 | var coreFmt = BitmapCorePixelFormat.Bgra32; 20 | 21 | var pxarr = Formats.Where(f => f.gdiFmt == bitmap.PixelFormat).ToArray(); 22 | if (pxarr.Length > 0) 23 | { 24 | var px = pxarr.First(); 25 | gdiFmt = px.gdiFmt; 26 | coreFmt = px.coreFmt; 27 | } 28 | 29 | var colorTable = bitmap.Palette.Entries.Select(e => new RGBQUAD { rgbBlue = e.B, rgbGreen = e.G, rgbRed = e.R }).ToArray(); 30 | 31 | var dlock = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, gdiFmt); 32 | var buf = (byte*)dlock.Scan0; 33 | 34 | BITMAP_WRITE_REQUEST req = new BITMAP_WRITE_REQUEST 35 | { 36 | dpiX = 0, 37 | dpiY = 0, 38 | imgWidth = bitmap.Width, 39 | imgHeight = bitmap.Height, 40 | imgStride = (uint)dlock.Stride, 41 | imgTopDown = true, 42 | imgColorTable = colorTable, 43 | }; 44 | 45 | var bytes = BitmapCore.WriteToBMP(ref req, buf, coreFmt, (uint)wFlags); 46 | bitmap.UnlockBits(dlock); 47 | return bytes; 48 | } 49 | 50 | /// 51 | public override unsafe Bitmap Read(byte* data, int dataLength, BitmapReaderFlags rFlags) 52 | { 53 | uint bcrFlags = (uint)rFlags; 54 | 55 | BITMAP_READ_DETAILS info; 56 | BitmapCore.ReadHeader(data, dataLength, out info, bcrFlags); 57 | 58 | byte* pixels = data + info.imgDataOffset; 59 | 60 | // we do this parsing here since BitmapCore has no references to System.Drawing 61 | if (info.compression == BitmapCompressionMode.BI_PNG || info.compression == BitmapCompressionMode.BI_JPEG) 62 | return new Bitmap(new PointerStream(pixels, info.imgDataSize)); 63 | 64 | // defaults 65 | PixelFormat gdiFmt = PixelFormat.Format32bppArgb; 66 | BitmapCorePixelFormat coreFmt = BitmapCorePixelFormat.Bgra32; 67 | 68 | var formatbgra32 = (rFlags & BitmapReaderFlags.ForceFormatBGRA32) > 0; 69 | if (!formatbgra32 && info.imgSourceFmt != null) 70 | { 71 | var origFmt = info.imgSourceFmt; 72 | if (origFmt == BitmapCorePixelFormat.Rgb24) 73 | { 74 | // we need BitmapCore to reverse the pixel order for GDI 75 | coreFmt = BitmapCorePixelFormat.Bgr24; 76 | gdiFmt = PixelFormat.Format24bppRgb; 77 | } 78 | else 79 | { 80 | var pxarr = Formats.Where(f => f.coreFmt == origFmt).ToArray(); 81 | if (pxarr.Length > 0) 82 | { 83 | var px = pxarr.First(); 84 | gdiFmt = px.gdiFmt; 85 | coreFmt = px.coreFmt; 86 | } 87 | } 88 | } 89 | 90 | Bitmap bitmap = new Bitmap(info.imgWidth, info.imgHeight, gdiFmt); 91 | var dlock = bitmap.LockBits(new Rectangle(0, 0, info.imgWidth, info.imgHeight), ImageLockMode.ReadWrite, gdiFmt); 92 | var buf = (byte*)dlock.Scan0; 93 | BitmapCore.ReadPixels(ref info, coreFmt, pixels, buf, bcrFlags); 94 | bitmap.UnlockBits(dlock); 95 | 96 | // update bitmap color palette 97 | var gdipPalette = bitmap.Palette; 98 | if (info.imgColorTable != null && gdipPalette?.Entries != null && gdipPalette.Entries.Length > 0) 99 | { 100 | for (int i = 0; i < info.imgColorTable.Length && i < gdipPalette.Entries.Length; i++) 101 | { 102 | var quad = info.imgColorTable[i]; 103 | gdipPalette.Entries[i] = Color.FromArgb(0xFF, quad.rgbRed, quad.rgbGreen, quad.rgbBlue); 104 | } 105 | bitmap.Palette = gdipPalette; 106 | } 107 | 108 | return bitmap; 109 | } 110 | 111 | private struct PxMap 112 | { 113 | public PixelFormat gdiFmt; 114 | public BitmapCorePixelFormat coreFmt; 115 | 116 | public PxMap(PixelFormat gdi, BitmapCorePixelFormat core) 117 | { 118 | gdiFmt = gdi; 119 | coreFmt = core; 120 | } 121 | } 122 | 123 | private static PxMap[] Formats = new PxMap[] 124 | { 125 | new PxMap(PixelFormat.Format32bppArgb, BitmapCorePixelFormat.Bgra32), 126 | new PxMap(PixelFormat.Format24bppRgb, BitmapCorePixelFormat.Bgr24), 127 | new PxMap(PixelFormat.Format16bppArgb1555, BitmapCorePixelFormat.Bgr5551), 128 | new PxMap(PixelFormat.Format16bppRgb555, BitmapCorePixelFormat.Bgr555X), 129 | new PxMap(PixelFormat.Format16bppRgb565, BitmapCorePixelFormat.Bgr565), 130 | new PxMap(PixelFormat.Format8bppIndexed, BitmapCorePixelFormat.Indexed8), 131 | new PxMap(PixelFormat.Format4bppIndexed, BitmapCorePixelFormat.Indexed4), 132 | new PxMap(PixelFormat.Format1bppIndexed, BitmapCorePixelFormat.Indexed1), 133 | }; 134 | } 135 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Gdi/ClipboardHandleGdi.cs: -------------------------------------------------------------------------------- 1 | using Clowd.Clipboard.Formats; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | 5 | namespace Clowd.Clipboard; 6 | 7 | /// 8 | /// Provides static methods for easy access to some of the most basic functionality of . 9 | /// 10 | [SupportedOSPlatform("windows")] 11 | public class ClipboardGdi : ClipboardStaticBase 12 | { 13 | private ClipboardGdi() { } 14 | } 15 | 16 | /// 17 | [SupportedOSPlatform("windows")] 18 | public abstract class ClipboardHandleGdiBase : ClipboardHandlePlatformBase 19 | { 20 | /// 21 | protected override IDataConverter GetDibConverter() => new DibToGdiBitmapConverter(); 22 | 23 | /// 24 | protected override IDataConverter GetDibV5Converter() => new DibV5ToGdiBitmapConverter(); 25 | 26 | /// 27 | protected override IDataConverter GetGdiHandleConverter() => new GdiHandleToGdiBitmapConverter(); 28 | 29 | /// 30 | protected override IDataConverter GetGifConverter() => new BytesToGdiBitmapConverter(ImageFormat.Gif); 31 | 32 | /// 33 | protected override IDataConverter GetJpegConverter() => new BytesToGdiBitmapConverter(ImageFormat.Jpeg); 34 | 35 | /// 36 | protected override IDataConverter GetPngConverter() => new BytesToGdiBitmapConverter(ImageFormat.Png); 37 | 38 | /// 39 | protected override IDataConverter GetTiffConverter() => new BytesToGdiBitmapConverter(ImageFormat.Tiff); 40 | 41 | /// 42 | protected override Bitmap LoadFromFile(string filePath) => Image.FromFile(filePath) as Bitmap; 43 | } 44 | 45 | /// 46 | [SupportedOSPlatform("windows")] 47 | public class ClipboardHandleGdi : ClipboardHandleGdiBase, IClipboardHandlePlatform 48 | { 49 | /// 50 | public virtual Bitmap GetImage() => GetImageImpl(); 51 | 52 | /// 53 | public virtual void SetImage(Bitmap bitmap) => SetImageImpl(bitmap); 54 | } 55 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Gdi/Clowd.Clipboard.Gdi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461;netstandard2.0;net6.0;net8.0 5 | true 6 | true 7 | Clowd.Clipboard 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Gdi/Formats/BytesToGdiBitmapConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | 4 | namespace Clowd.Clipboard.Formats; 5 | 6 | /// 7 | /// Converts bitmap bytes to a specific encoder format: eg. PNG or TIFF 8 | /// 9 | [SupportedOSPlatform("windows")] 10 | public class BytesToGdiBitmapConverter : BytesDataConverterBase 11 | { 12 | private readonly ImageFormat format; 13 | 14 | /// 15 | /// Creates a with the specified encoding. 16 | /// 17 | public BytesToGdiBitmapConverter(ImageFormat format) 18 | { 19 | this.format = format; 20 | } 21 | 22 | /// 23 | public override Bitmap ReadFromBytes(byte[] data) 24 | { 25 | using var ms = new MemoryStream(data); 26 | return (Bitmap)Bitmap.FromStream(ms); 27 | } 28 | 29 | /// 30 | public override byte[] WriteToBytes(Bitmap obj) 31 | { 32 | using var ms = new MemoryStream(); 33 | obj.Save(ms, format); 34 | return ms.ToArray(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Gdi/Formats/DibToGdiBitmapConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | using System.Runtime.InteropServices; 4 | using Clowd.Clipboard.Bitmaps; 5 | 6 | namespace Clowd.Clipboard.Formats; 7 | 8 | /// 9 | /// Converts a CF_DIB to/from a WPF BitmapSource. 10 | /// 11 | [SupportedOSPlatform("windows")] 12 | public unsafe class DibToGdiBitmapConverter : BytesDataConverterBase 13 | { 14 | /// 15 | public override Bitmap ReadFromBytes(byte[] data) 16 | { 17 | return BitmapGdi.FromBytes(data, BitmapReaderFlags.PreserveInvalidAlphaChannel); 18 | } 19 | 20 | /// 21 | public override byte[] WriteToBytes(Bitmap bmp) 22 | { 23 | // use GDI because the integrated method is less flexible. 24 | // for example, it can't output pre-multiplied alpha which seems to produce better results. 25 | // return BitmapGdi.ToBytes(obj, BitmapWriterFlags.ForceInfoHeader | BitmapWriterFlags.SkipFileHeader); 26 | 27 | var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb); 28 | 29 | try 30 | { 31 | int imgSize = data.Stride * data.Height; 32 | byte[] imgBytes = new byte[imgSize]; 33 | Marshal.Copy(data.Scan0, imgBytes, 0, imgSize); 34 | 35 | BITMAPINFOHEADER info = new BITMAPINFOHEADER() 36 | { 37 | bV5Size = 40, 38 | bV5BitCount = 32, 39 | bV5Compression = BitmapCompressionMode.BI_RGB, 40 | bV5Height = data.Height, 41 | bV5Width = data.Width, 42 | bV5Planes = 1, 43 | bV5SizeImage = (uint)imgSize, 44 | }; 45 | 46 | var headerSize = Marshal.SizeOf(); 47 | byte[] buf = new byte[headerSize + imgSize]; 48 | uint offset = 0; 49 | StructUtil.SerializeTo(info, buf, ref offset); 50 | 51 | // the bitmap is upside down, so we need to reverse it. 52 | for (int y = 0; y < data.Height; y++) 53 | { 54 | Buffer.BlockCopy(imgBytes, (data.Height - y - 1) * data.Stride, buf, headerSize + (y * data.Stride), data.Stride); 55 | } 56 | 57 | return buf; 58 | } 59 | finally 60 | { 61 | bmp.UnlockBits(data); 62 | } 63 | } 64 | } 65 | 66 | /// 67 | /// Converts a CF_DIBV5 to/from a WPF BitmapSource. 68 | /// 69 | [SupportedOSPlatform("windows")] 70 | public unsafe class DibV5ToGdiBitmapConverter : DibToGdiBitmapConverter 71 | { 72 | /// 73 | public override byte[] WriteToBytes(Bitmap obj) 74 | { 75 | return BitmapGdi.ToBytes(obj, BitmapWriterFlags.ForceV5Header | BitmapWriterFlags.SkipFileHeader); 76 | } 77 | } -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Gdi/Formats/GdiHandleToGdiBitmapConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace Clowd.Clipboard.Formats; 4 | 5 | /// 6 | /// Data converter for translating CF_BITMAP (gdi image handle) into a WPF BitmapSource. 7 | /// 8 | [SupportedOSPlatform("windows")] 9 | public class GdiHandleToGdiBitmapConverter : IDataConverter 10 | { 11 | /// 12 | public Bitmap ReadFromHGlobal(IntPtr hGlobal) 13 | { 14 | return Bitmap.FromHbitmap(hGlobal); 15 | } 16 | 17 | /// 18 | public IntPtr WriteToHGlobal(Bitmap obj) 19 | { 20 | throw new NotSupportedException("Should always write a DIB to the clipboard instead of a DDB."); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Wpf/Bitmaps/BitmapWpf.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Media.Imaging; 2 | 3 | namespace Clowd.Clipboard.Bitmaps; 4 | 5 | /// 6 | /// Provides a WPF implementation of Bitmap reader and writer. This bitmap library can read almost any kind of bitmap and 7 | /// tries to do a better job than WPF does in terms of coverage and it also tries to handle some nuances of how other native applications write bitmaps, 8 | /// especially when reading from or writing to the clipboard. 9 | /// 10 | public sealed class BitmapWpf : BitmapConverterStaticBase 11 | { 12 | /// 13 | public unsafe override BitmapSource Read(byte* data, int dataLength, BitmapReaderFlags rFlags) 14 | { 15 | BITMAP_READ_DETAILS info; 16 | uint bcrFlags = (uint)rFlags; 17 | BitmapCore.ReadHeader(data, dataLength, out info, bcrFlags); 18 | return BitmapWpfInternal.Read(ref info, data + info.imgDataOffset, bcrFlags); 19 | } 20 | 21 | /// 22 | public override byte[] GetBytes(BitmapSource bitmap, BitmapWriterFlags wFlags) 23 | { 24 | return BitmapWpfInternal.GetBytes(bitmap, (uint)wFlags); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Wpf/Bitmaps/BitmapWpfInternal.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | using System.Windows.Media; 5 | using System.Windows.Media.Imaging; 6 | 7 | namespace Clowd.Clipboard.Bitmaps; 8 | 9 | // this class exists separately so it can be included as a submodule/file in ClipboardGapWpf and not create conflicts upstream - rather than including as a project. 10 | internal class BitmapWpfInternal 11 | { 12 | public unsafe static BitmapSource Read(ref BITMAP_READ_DETAILS info, byte* pixels, uint bcrFlags) 13 | { 14 | // we do this parsing here since BitmapCore has no references to PresentationCore 15 | if (info.compression == BitmapCompressionMode.BI_PNG) 16 | { 17 | var stream = new PointerStream(pixels, info.imgDataSize); 18 | var png = new PngBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); 19 | return png.Frames[0]; 20 | } 21 | else if (info.compression == BitmapCompressionMode.BI_JPEG) 22 | { 23 | var stream = new PointerStream(pixels, info.imgDataSize); 24 | var jpg = new JpegBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); 25 | return jpg.Frames[0]; 26 | } 27 | 28 | PixelFormat wpfFmt = PixelFormats.Bgra32; 29 | BitmapCorePixelFormat coreFmt = BitmapCorePixelFormat.Bgra32; 30 | 31 | bool forceBgra32 = (bcrFlags & BitmapCore.BC_READ_FORCE_BGRA32) > 0; 32 | if (!forceBgra32 && info.imgSourceFmt != null) 33 | { 34 | var origFmt = info.imgSourceFmt; 35 | var pxarr = Formats.Where(m => m.coreFmt == origFmt).ToArray(); 36 | if (pxarr.Length > 0) 37 | { 38 | var px = pxarr.First(); 39 | wpfFmt = px.wpfFmt; 40 | coreFmt = px.coreFmt; 41 | } 42 | } 43 | 44 | BitmapPalette palette = null; 45 | if (info.imgColorTable.Length > 0) 46 | { 47 | var clrs = info.imgColorTable.Select(c => Color.FromRgb(c.rgbRed, c.rgbGreen, c.rgbBlue)); 48 | if (info.imgColorTable.Length > 256) // wpf throws on oversized palettes 49 | clrs = clrs.Take(256); 50 | palette = new BitmapPalette(clrs.ToList()); 51 | } 52 | 53 | var bitmap = new WriteableBitmap( 54 | info.imgWidth, 55 | info.imgHeight, 56 | info.dpiX, 57 | info.dpiY, 58 | wpfFmt, 59 | palette); 60 | 61 | var buf = (byte*)bitmap.BackBuffer; 62 | 63 | bitmap.Lock(); 64 | 65 | BitmapCore.ReadPixels(ref info, coreFmt, pixels, buf, bcrFlags); 66 | 67 | bitmap.AddDirtyRect(new System.Windows.Int32Rect(0, 0, info.imgWidth, info.imgHeight)); 68 | bitmap.Unlock(); 69 | bitmap.Freeze(); // dispose back buffer 70 | 71 | return bitmap; 72 | } 73 | 74 | private struct PxMap 75 | { 76 | public PixelFormat wpfFmt; 77 | public BitmapCorePixelFormat coreFmt; 78 | 79 | public PxMap(PixelFormat wpf, BitmapCorePixelFormat core) 80 | { 81 | wpfFmt = wpf; 82 | coreFmt = core; 83 | } 84 | } 85 | 86 | private static PxMap[] Formats = new PxMap[] 87 | { 88 | new PxMap(PixelFormats.Bgra32, BitmapCorePixelFormat.Bgra32), 89 | new PxMap(PixelFormats.Rgb24, BitmapCorePixelFormat.Rgb24), 90 | new PxMap(PixelFormats.Bgr24, BitmapCorePixelFormat.Bgr24), 91 | new PxMap(PixelFormats.Bgr555, BitmapCorePixelFormat.Bgr555X), 92 | new PxMap(PixelFormats.Bgr565, BitmapCorePixelFormat.Bgr565), 93 | new PxMap(PixelFormats.Indexed8, BitmapCorePixelFormat.Indexed8), 94 | new PxMap(PixelFormats.Indexed4, BitmapCorePixelFormat.Indexed4), 95 | new PxMap(PixelFormats.Indexed2, BitmapCorePixelFormat.Indexed2), 96 | new PxMap(PixelFormats.Indexed1, BitmapCorePixelFormat.Indexed1), 97 | }; 98 | 99 | public static unsafe byte[] GetBytes(BitmapSource bitmap, uint bcrFlags) 100 | { 101 | uint stride = StructUtil.CalcStride((ushort)bitmap.Format.BitsPerPixel, bitmap.PixelWidth); 102 | 103 | byte[] buffer = new byte[stride * bitmap.PixelHeight]; 104 | bitmap.CopyPixels(buffer, (int)stride, 0); 105 | 106 | var clrs = bitmap.Palette == null ? null : bitmap.Palette.Colors.Select(c => new RGBQUAD { rgbRed = c.R, rgbBlue = c.B, rgbGreen = c.G }).ToArray(); 107 | 108 | BITMAP_WRITE_REQUEST req = new BITMAP_WRITE_REQUEST 109 | { 110 | dpiX = bitmap.DpiX, 111 | dpiY = bitmap.DpiY, 112 | imgWidth = bitmap.PixelWidth, 113 | imgHeight = bitmap.PixelHeight, 114 | imgStride = stride, 115 | imgTopDown = true, 116 | imgColorTable = clrs, 117 | }; 118 | 119 | BITMASKS masks = default; 120 | 121 | uint getBitmask(IList mask) 122 | { 123 | uint result = 0; 124 | int shift = 0; 125 | for (int i = 0; i < mask.Count; i++) 126 | { 127 | result = result | (uint)(mask[i] << shift); 128 | shift += 8; 129 | } 130 | return result; 131 | } 132 | 133 | if (bitmap.Format.Masks != null && bitmap.Format.Masks.Count == 3) 134 | { 135 | var wpfmasks = bitmap.Format.Masks; 136 | masks.maskBlue = getBitmask(wpfmasks[0].Mask); 137 | masks.maskGreen = getBitmask(wpfmasks[1].Mask); 138 | masks.maskRed = getBitmask(wpfmasks[2].Mask); 139 | } 140 | else if (bitmap.Format.Masks != null && bitmap.Format.Masks.Count == 4) 141 | { 142 | var wpfmasks = bitmap.Format.Masks; 143 | masks.maskBlue = getBitmask(wpfmasks[0].Mask); 144 | masks.maskGreen = getBitmask(wpfmasks[1].Mask); 145 | masks.maskRed = getBitmask(wpfmasks[2].Mask); 146 | masks.maskAlpha = getBitmask(wpfmasks[3].Mask); 147 | } 148 | 149 | fixed (byte* ptr = buffer) 150 | return BitmapCore.WriteToBMP(ref req, ptr, masks, (ushort)bitmap.Format.BitsPerPixel, bcrFlags); 151 | } 152 | 153 | private class BitmapWpfColorManagement 154 | { 155 | [DllImport("WindowsCodecs", EntryPoint = "WICCreateImagingFactory_Proxy")] 156 | private static extern int CreateImagingFactory(UInt32 SDKVersion, out IntPtr ppICodecFactory); 157 | 158 | [DllImport("WindowsCodecs", EntryPoint = "WICCreateColorContext_Proxy")] 159 | private static extern int /* HRESULT */ CreateColorContext(IntPtr pICodecFactory, out IntPtr /* IWICColorContext */ ppColorContext); 160 | 161 | [DllImport("WindowsCodecs", EntryPoint = "IWICColorContext_InitializeFromMemory_Proxy")] 162 | private unsafe static extern int /* HRESULT */ InitializeFromMemory(IntPtr THIS_PTR, void* pbBuffer, uint cbBufferSize); 163 | 164 | private const int WINCODEC_SDK_VERSION = 0x0236; 165 | 166 | public unsafe static ColorContext GetWpfColorContext(void* profilePtr, uint profileSize) 167 | { 168 | IntPtr factoryPtr, colorContextPtr; 169 | 170 | var hr = CreateImagingFactory(WINCODEC_SDK_VERSION, out factoryPtr); 171 | if (hr != 0) throw new Win32Exception(hr); 172 | 173 | try 174 | { 175 | hr = CreateColorContext(factoryPtr, out colorContextPtr); 176 | if (hr != 0) throw new Win32Exception(hr); 177 | 178 | try 179 | { 180 | hr = InitializeFromMemory(colorContextPtr, profilePtr, profileSize); 181 | if (hr != 0) throw new Win32Exception(hr); 182 | 183 | var colorContextType = typeof(ColorContext); 184 | var milHandleType = colorContextType.Assembly.GetType("System.Windows.Media.SafeMILHandle"); 185 | 186 | var milHandle = Activator.CreateInstance(milHandleType, BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { colorContextPtr }, null); 187 | var colorContext = Activator.CreateInstance(colorContextType, BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { milHandle }, null); 188 | 189 | return (ColorContext)colorContext; 190 | } 191 | catch 192 | { 193 | // Only free colorContextPtr if there is an error. Otherwise, it will be freed by WPF 194 | Marshal.Release(colorContextPtr); 195 | throw; 196 | } 197 | } 198 | finally 199 | { 200 | Marshal.Release(factoryPtr); 201 | } 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Wpf/ClipboardHandleWpf.cs: -------------------------------------------------------------------------------- 1 | using Clowd.Clipboard.Formats; 2 | using System.Windows.Media.Imaging; 3 | 4 | namespace Clowd.Clipboard; 5 | 6 | /// 7 | /// Provides static methods for easy access to some of the most basic functionality of . 8 | /// 9 | public class ClipboardWpf : ClipboardStaticBase 10 | { 11 | private ClipboardWpf() { } 12 | } 13 | 14 | /// 15 | public class ClipboardHandleWpf : ClipboardHandlePlatformBase, IClipboardHandlePlatform 16 | { 17 | /// 18 | public void SetImage(BitmapSource bitmap) => SetImageImpl(bitmap); 19 | 20 | /// 21 | public BitmapSource GetImage() => GetImageImpl(); 22 | 23 | /// 24 | protected override BitmapSource LoadFromFile(string filePath) 25 | { 26 | var bi = new BitmapImage(); 27 | bi.BeginInit(); 28 | bi.UriSource = new Uri(filePath); 29 | bi.CacheOption = BitmapCacheOption.OnLoad; 30 | bi.EndInit(); 31 | return bi; 32 | } 33 | 34 | /// 35 | protected override IDataConverter GetJpegConverter() => new BytesToWicBitmapConverter(BytesToWicBitmapConverter.Format_Jpeg); 36 | 37 | /// 38 | protected override IDataConverter GetTiffConverter() => new BytesToWicBitmapConverter(BytesToWicBitmapConverter.Format_Tiff); 39 | 40 | /// 41 | protected override IDataConverter GetGifConverter() => new BytesToWicBitmapConverter(BytesToWicBitmapConverter.Format_Gif); 42 | 43 | /// 44 | protected override IDataConverter GetPngConverter() => new BytesToWicBitmapConverter(BytesToWicBitmapConverter.Format_Png); 45 | 46 | /// 47 | protected override IDataConverter GetGdiHandleConverter() => new GdiHandleToWicBitmapConverter(); 48 | 49 | /// 50 | protected override IDataConverter GetDibConverter() => new DibToWicBitmapConverter(); 51 | 52 | /// 53 | protected override IDataConverter GetDibV5Converter() => new DibV5ToWicBitmapConverter(); 54 | } 55 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Wpf/Clowd.Clipboard.Wpf.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461;net6.0-windows;net8.0-windows 5 | true 6 | true 7 | true 8 | Clowd.Clipboard 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Wpf/Formats/BytesToWicBitmapConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Media.Imaging; 2 | 3 | namespace Clowd.Clipboard.Formats; 4 | 5 | /// 6 | /// Base class for encoding images to/from bytes using the WPF/WIC encoder classes. 7 | /// 8 | public class BytesToWicBitmapConverter : BytesDataConverterBase 9 | { 10 | 11 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 12 | // from wincodec.h 13 | public static readonly Guid Format_Bmp = new Guid(0x0af1d87e, 0xfcfe, 0x4188, 0xbd, 0xeb, 0xa7, 0x90, 0x64, 0x71, 0xcb, 0xe3); 14 | public static readonly Guid Format_Png = new Guid(0x1b7cfaf4, 0x713f, 0x473c, 0xbb, 0xcd, 0x61, 0x37, 0x42, 0x5f, 0xae, 0xaf); 15 | public static readonly Guid Format_Ico = new Guid(0xa3a860c4, 0x338f, 0x4c17, 0x91, 0x9a, 0xfb, 0xa4, 0xb5, 0x62, 0x8f, 0x21); 16 | public static readonly Guid Format_Jpeg = new Guid(0x19e4a5aa, 0x5662, 0x4fc5, 0xa0, 0xc0, 0x17, 0x58, 0x02, 0x8e, 0x10, 0x57); 17 | public static readonly Guid Format_Tiff = new Guid(0x163bcc30, 0xe2e9, 0x4f0b, 0x96, 0x1d, 0xa3, 0xe9, 0xfd, 0xb7, 0x88, 0xa3); 18 | public static readonly Guid Format_Gif = new Guid(0x1f8a5601, 0x7d4d, 0x4cbd, 0x9c, 0x82, 0x1b, 0xc8, 0xd4, 0xee, 0xb9, 0xa5); 19 | public static readonly Guid Format_Wmp = new Guid(0x57a37caa, 0x367a, 0x4540, 0x91, 0x6b, 0xf1, 0x83, 0xc5, 0x09, 0x3a, 0x4b); 20 | public static readonly Guid Format_Dds = new Guid(0x9967cb95, 0x2e85, 0x4ac8, 0x8c, 0xa2, 0x83, 0xd7, 0xcc, 0xd4, 0x25, 0xc9); 21 | public static readonly Guid Format_Adng = new Guid(0xf3ff6d0d, 0x38c0, 0x41c4, 0xb1, 0xfe, 0x1f, 0x38, 0x24, 0xf1, 0x7b, 0x84); 22 | public static readonly Guid Format_Heif = new Guid(0xe1e62521, 0x6787, 0x405b, 0xa3, 0x39, 0x50, 0x07, 0x15, 0xb5, 0x76, 0x3f); 23 | public static readonly Guid Format_Webp = new Guid(0xe094b0e2, 0x67f2, 0x45b3, 0xb0, 0xea, 0x11, 0x53, 0x37, 0xca, 0x7c, 0xf3); 24 | public static readonly Guid Format_Raw = new Guid(0xfe99ce60, 0xf19c, 0x433c, 0xa3, 0xae, 0x00, 0xac, 0xef, 0xa9, 0xca, 0x21); 25 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member 26 | 27 | private readonly Guid _containerFormat; 28 | 29 | /// 30 | /// Create a new de/encoder with the specified container format. 31 | /// 32 | public BytesToWicBitmapConverter(Guid containerFormat) 33 | { 34 | _containerFormat = containerFormat; 35 | } 36 | 37 | /// 38 | public override BitmapSource ReadFromBytes(byte[] data) 39 | { 40 | var decoder = GetDecoder(new MemoryStream(data)); 41 | BitmapSource bitmapSource = decoder.Frames[0]; 42 | return bitmapSource; 43 | } 44 | 45 | /// 46 | public override byte[] WriteToBytes(BitmapSource obj) 47 | { 48 | var stream = new MemoryStream(); 49 | var encoder = GetEncoder(); 50 | if (obj is BitmapFrame frame) encoder.Frames.Add(frame); 51 | else encoder.Frames.Add(BitmapFrame.Create(obj)); 52 | encoder.Save(stream); 53 | return stream.GetBuffer(); 54 | } 55 | 56 | /// 57 | /// Creates an image decoder from the specified stream. 58 | /// 59 | protected virtual BitmapDecoder GetDecoder(Stream stream) 60 | { 61 | return BitmapDecoder.Create(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); 62 | } 63 | 64 | /// 65 | /// Creates an image encoder. 66 | /// 67 | protected virtual BitmapEncoder GetEncoder() 68 | { 69 | return BitmapEncoder.Create(_containerFormat); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Wpf/Formats/DibToWicBitmapConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Windows.Media; 3 | using System.Windows.Media.Imaging; 4 | using Clowd.Clipboard.Bitmaps; 5 | 6 | namespace Clowd.Clipboard.Formats; 7 | 8 | /// 9 | /// Converts a CF_DIB to/from a WPF BitmapSource. 10 | /// 11 | public unsafe class DibToWicBitmapConverter : BytesDataConverterBase 12 | { 13 | /// 14 | public override BitmapSource ReadFromBytes(byte[] data) 15 | { 16 | fixed (byte* dataptr = data) 17 | { 18 | uint bcrFlags = BitmapCore.BC_READ_PRESERVE_INVALID_ALPHA; 19 | BitmapCore.ReadHeader(dataptr, data.Length, out var info, bcrFlags); 20 | return BitmapWpfInternal.Read(ref info, (dataptr + info.imgDataOffset), bcrFlags); 21 | } 22 | } 23 | 24 | /// 25 | public override byte[] WriteToBytes(BitmapSource bmp) 26 | { 27 | // use WIC because the integrated method is less flexible. 28 | // for example, it can't output pre-multiplied alpha which seems to produce better results. 29 | //return BitmapWpfInternal.GetBytes(obj, BitmapCore.BC_WRITE_SKIP_FH | BitmapCore.BC_WRITE_VINFO); 30 | 31 | FormatConvertedBitmap formatted = new FormatConvertedBitmap(bmp, PixelFormats.Pbgra32, null, 0); 32 | int imgHeight = formatted.PixelHeight; 33 | int imgWidth = formatted.PixelWidth; 34 | int imgStride = imgWidth * 4; 35 | int imgSize = imgStride * imgHeight; 36 | var imgBytes = new byte[imgSize]; 37 | formatted.CopyPixels(imgBytes, imgStride, 0); 38 | 39 | BITMAPINFOHEADER info = new BITMAPINFOHEADER() 40 | { 41 | bV5Size = 40, 42 | bV5BitCount = 32, 43 | bV5Compression = BitmapCompressionMode.BI_RGB, 44 | bV5Height = imgHeight, 45 | bV5Width = imgWidth, 46 | bV5Planes = 1, 47 | bV5SizeImage = (uint)imgSize, 48 | }; 49 | 50 | var headerSize = Marshal.SizeOf(); 51 | byte[] buf = new byte[headerSize + imgSize]; 52 | uint offset = 0; 53 | StructUtil.SerializeTo(info, buf, ref offset); 54 | 55 | // the bitmap is upside down, so we need to reverse it. 56 | for (int y = 0; y < imgHeight; y++) 57 | { 58 | Buffer.BlockCopy(imgBytes, (imgHeight - y - 1) * imgStride, buf, headerSize + (y * imgStride), imgStride); 59 | } 60 | 61 | return buf; 62 | } 63 | } 64 | 65 | /// 66 | /// Converts a CF_DIBV5 to/from a WPF BitmapSource. 67 | /// 68 | public unsafe class DibV5ToWicBitmapConverter : DibToWicBitmapConverter 69 | { 70 | /// 71 | public override byte[] WriteToBytes(BitmapSource obj) 72 | { 73 | return BitmapWpfInternal.GetBytes(obj, BitmapCore.BC_WRITE_SKIP_FH | BitmapCore.BC_WRITE_V5); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard.Wpf/Formats/GdiHandleToWicBitmapConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Windows.Media.Imaging; 3 | using PixelFormats = System.Windows.Media.PixelFormats; 4 | using PixelFormat = System.Drawing.Imaging.PixelFormat; 5 | using ImageLockMode = System.Drawing.Imaging.ImageLockMode; 6 | 7 | namespace Clowd.Clipboard.Formats; 8 | 9 | /// 10 | /// Data converter for translating CF_BITMAP (gdi image handle) into a WPF BitmapSource. 11 | /// 12 | public class GdiHandleToWicBitmapConverter : IDataConverter 13 | { 14 | /// 15 | public BitmapSource ReadFromHGlobal(IntPtr hGlobal) 16 | { 17 | using var bitmap = Bitmap.FromHbitmap(hGlobal); 18 | 19 | var bitmapData = bitmap.LockBits( 20 | new Rectangle(0, 0, bitmap.Width, bitmap.Height), 21 | ImageLockMode.ReadOnly, 22 | PixelFormat.Format32bppArgb); 23 | 24 | var bitmapSource = BitmapSource.Create( 25 | bitmapData.Width, 26 | bitmapData.Height, 27 | bitmap.HorizontalResolution, 28 | bitmap.VerticalResolution, 29 | PixelFormats.Bgra32, 30 | null, 31 | bitmapData.Scan0, 32 | bitmapData.Stride * bitmapData.Height, 33 | bitmapData.Stride); 34 | 35 | bitmap.UnlockBits(bitmapData); 36 | 37 | return bitmapSource; 38 | } 39 | 40 | /// 41 | public IntPtr WriteToHGlobal(BitmapSource obj) 42 | { 43 | throw new NotSupportedException("Should always write a DIB to the clipboard instead of a DDB."); 44 | //var bitmap = new FormatConvertedBitmap(obj, PixelFormats.Bgra32, null, 0); 45 | //using var gdi = new Bitmap(bitmap.PixelWidth, bitmap.PixelHeight, PixelFormat.Format32bppArgb); 46 | 47 | //var bitmapData = gdi.LockBits( 48 | // new Rectangle(0, 0, gdi.Width, gdi.Height), 49 | // ImageLockMode.ReadOnly, 50 | // PixelFormat.Format32bppArgb); 51 | 52 | //obj.CopyPixels( 53 | // new Int32Rect(0, 0, bitmapData.Width, bitmapData.Height), 54 | // bitmapData.Scan0, 55 | // bitmapData.Stride, 56 | // 0); 57 | 58 | //// this sill not work. 59 | //// https://stackoverflow.com/questions/35154938/how-to-place-gdi-bitmap-onto-the-clipboard?rq=1 60 | //return gdi.GetHbitmap(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Bitmaps/BitmapCorePixelFormat.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 2 | namespace Clowd.Clipboard.Bitmaps; 3 | 4 | public class BitFields 5 | { 6 | public static readonly BITMASKS BITFIELDS_RGB_24 = new BITMASKS(0xff, 0xff00, 0xff0000); 7 | public static readonly BITMASKS BITFIELDS_BGR_24 = new BITMASKS(0xff0000, 0xff00, 0xff); 8 | public static readonly BITMASKS BITFIELDS_BGRA_32 = new BITMASKS(0xff0000, 0xff00, 0xff, 0xff000000); 9 | public static readonly BITMASKS BITFIELDS_BGR_565 = new BITMASKS(0xf800, 0x7e0, 0x1f); 10 | public static readonly BITMASKS BITFIELDS_BGRA_5551 = new BITMASKS(0x7c00, 0x03e0, 0x001f, 0x8000); 11 | public static readonly BITMASKS BITFIELDS_BGRA_555X = new BITMASKS(0x7c00, 0x03e0, 0x001f); 12 | } 13 | 14 | public unsafe delegate byte* WritePixelToPtr(byte* ptr, byte b, byte g, byte r, byte a); 15 | 16 | public unsafe class BitmapCorePixelFormat : IEquatable 17 | { 18 | public bool IsIndexed => BitsPerPixel < 16; 19 | public bool HasAlpha => Masks.maskAlpha != 0; 20 | 21 | public mscms.mscmsPxFormat? MscmsFormat { get; private set; } 22 | public BITMASKS Masks { get; private set; } 23 | public WritePixelToPtr Write { get; private set; } 24 | public ushort BitsPerPixel { get; private set; } 25 | 26 | private BitmapCorePixelFormat() { } 27 | 28 | public static readonly BitmapCorePixelFormat Indexed1 = new BitmapCorePixelFormat 29 | { 30 | BitsPerPixel = 1, 31 | }; 32 | 33 | public static readonly BitmapCorePixelFormat Indexed2 = new BitmapCorePixelFormat 34 | { 35 | BitsPerPixel = 2, 36 | }; 37 | 38 | public static readonly BitmapCorePixelFormat Indexed4 = new BitmapCorePixelFormat 39 | { 40 | BitsPerPixel = 4, 41 | }; 42 | 43 | public static readonly BitmapCorePixelFormat Indexed8 = new BitmapCorePixelFormat 44 | { 45 | BitsPerPixel = 8, 46 | }; 47 | 48 | public static readonly BitmapCorePixelFormat Bgr555X = new BitmapCorePixelFormat 49 | { 50 | MscmsFormat = mscms.mscmsPxFormat.BM_x555RGB, 51 | BitsPerPixel = 16, 52 | Masks = BitFields.BITFIELDS_BGRA_555X, 53 | Write = (ptr, b, g, r, a) => 54 | { 55 | const ushort max5 = 0x1F; 56 | const double mult5 = max5 / 255d; 57 | 58 | ushort* dest = (ushort*)ptr; 59 | 60 | byte cb = (byte)Math.Ceiling(b * mult5); 61 | byte cg = (byte)Math.Ceiling(g * mult5); 62 | byte cr = (byte)Math.Ceiling(r * mult5); 63 | 64 | *dest += (ushort)(b | g << 5 | r << 10); 65 | 66 | return (byte*)dest; 67 | }, 68 | }; 69 | 70 | public static readonly BitmapCorePixelFormat Bgr5551 = new BitmapCorePixelFormat 71 | { 72 | MscmsFormat = mscms.mscmsPxFormat.BM_x555RGB, 73 | BitsPerPixel = 16, 74 | Masks = BitFields.BITFIELDS_BGRA_5551, 75 | Write = (ptr, b, g, r, a) => 76 | { 77 | const ushort max5 = 0x1F; 78 | const double mult5 = max5 / 255d; 79 | 80 | ushort* dest = (ushort*)ptr; 81 | 82 | byte cb = (byte)Math.Ceiling(b * mult5); 83 | byte cg = (byte)Math.Ceiling(g * mult5); 84 | byte cr = (byte)Math.Ceiling(r * mult5); 85 | byte ca = a > 0 ? (byte)1 : (byte)0; 86 | 87 | *dest += (ushort)(b | g << 5 | r << 10 | a << 15); 88 | 89 | return (byte*)dest; 90 | }, 91 | }; 92 | 93 | public static readonly BitmapCorePixelFormat Bgr565 = new BitmapCorePixelFormat 94 | { 95 | MscmsFormat = mscms.mscmsPxFormat.BM_565RGB, 96 | BitsPerPixel = 16, 97 | Masks = BitFields.BITFIELDS_BGR_565, 98 | Write = (ptr, b, g, r, a) => 99 | { 100 | const ushort max5 = 0x1F; 101 | const ushort max6 = 0x3F; 102 | const double mult5 = max5 / 255d; 103 | const double mult6 = max6 / 255d; 104 | 105 | ushort* dest = (ushort*)ptr; 106 | 107 | byte cb = (byte)Math.Ceiling(b * mult5); 108 | byte cg = (byte)Math.Ceiling(g * mult6); 109 | byte cr = (byte)Math.Ceiling(r * mult5); 110 | 111 | *dest += (ushort)(b | g << 5 | r << 11); 112 | 113 | return (byte*)dest; 114 | }, 115 | }; 116 | 117 | public static readonly BitmapCorePixelFormat Rgb24 = new BitmapCorePixelFormat 118 | { 119 | MscmsFormat = mscms.mscmsPxFormat.BM_BGRTRIPLETS, 120 | BitsPerPixel = 24, 121 | Masks = BitFields.BITFIELDS_RGB_24, 122 | Write = (ptr, b, g, r, a) => 123 | { 124 | *ptr++ = r; 125 | *ptr++ = g; 126 | *ptr++ = b; 127 | return ptr; 128 | }, 129 | }; 130 | 131 | public static readonly BitmapCorePixelFormat Bgr24 = new BitmapCorePixelFormat 132 | { 133 | MscmsFormat = mscms.mscmsPxFormat.BM_RGBTRIPLETS, 134 | BitsPerPixel = 24, 135 | Masks = BitFields.BITFIELDS_BGR_24, 136 | Write = (ptr, b, g, r, a) => 137 | { 138 | *ptr++ = b; 139 | *ptr++ = g; 140 | *ptr++ = r; 141 | return ptr; 142 | }, 143 | }; 144 | 145 | public static readonly BitmapCorePixelFormat Bgra32 = new BitmapCorePixelFormat 146 | { 147 | MscmsFormat = mscms.mscmsPxFormat.BM_xRGBQUADS, 148 | BitsPerPixel = 32, 149 | Masks = BitFields.BITFIELDS_BGRA_32, 150 | Write = (ptr, b, g, r, a) => 151 | { 152 | uint* dest = (uint*)ptr; 153 | *dest++ = (uint)(b | g << 8 | r << 16 | a << 24); 154 | return (byte*)dest; 155 | }, 156 | }; 157 | 158 | public static readonly BitmapCorePixelFormat[] Formats = new BitmapCorePixelFormat[] 159 | { 160 | Indexed1, 161 | Indexed2, 162 | Indexed4, 163 | Indexed8, 164 | Bgr555X, 165 | Bgr5551, 166 | Bgr565, 167 | Rgb24, 168 | Bgr24, 169 | Bgra32, 170 | }; 171 | 172 | public bool IsMatch(ushort bits, BITMASKS masks) 173 | { 174 | if (bits != BitsPerPixel) 175 | return false; 176 | 177 | if (IsIndexed) 178 | { 179 | return true; 180 | } 181 | else 182 | { 183 | return masks.Equals(Masks); 184 | } 185 | } 186 | 187 | public override bool Equals(object obj) 188 | { 189 | if (obj is BitmapCorePixelFormat fmt) return fmt.Equals(this); 190 | return false; 191 | } 192 | 193 | public bool Equals(BitmapCorePixelFormat other) 194 | { 195 | if (other.BitsPerPixel != BitsPerPixel) 196 | return false; 197 | 198 | return other.Masks.Equals(Masks); 199 | } 200 | 201 | public override int GetHashCode() 202 | { 203 | unchecked 204 | { 205 | int hash = 13; 206 | hash = hash * 7 + BitsPerPixel.GetHashCode(); 207 | hash = hash * 7 + Masks.GetHashCode(); 208 | return hash; 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Bitmaps/IBitmapConverter.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Clowd.Clipboard.Bitmaps; 3 | 4 | /// 5 | /// Flags for customizing behavior when reading data into a Bitmap implementation. 6 | /// 7 | [Flags] 8 | public enum BitmapReaderFlags : uint 9 | { 10 | /// 11 | /// No special parsing flags 12 | /// 13 | None = 0, 14 | 15 | /// 16 | /// In windows, many applications create 16 or 32 bpp Bitmaps that indicate they have no transparency, but actually do have transparency. 17 | /// For example, in a 32bpp RGB encoded bitmap, you'd have the following R8, G8, B8, and the remaining 8 bits are to be ignored / zero. 18 | /// Sometimes, these bits are not zero, and with this flag set, we will use heuristics to determine if that unused channel contains 19 | /// transparency data, and if so, parse it as such. 20 | /// 21 | PreserveInvalidAlphaChannel = BitmapCore.BC_READ_PRESERVE_INVALID_ALPHA, 22 | 23 | /// 24 | /// Will cause an exeption if the original pixel format can not be preserved. This could be the case if BitmapCore or the target framework 25 | /// does not support this format natively. 26 | /// 27 | StrictPreserveOriginalFormat = BitmapCore.BC_READ_STRICT_PRESERVE_FORMAT, 28 | 29 | /// 30 | /// Will force the bitmap pixel data to be converted to BGRA32 no matter what the source format is. Not valid if combined with . 31 | /// 32 | ForceFormatBGRA32 = BitmapCore.BC_READ_FORCE_BGRA32, 33 | 34 | /// 35 | /// Skips and ignores any embedded calibration or ICC profile data. 36 | /// 37 | IgnoreColorProfile = BitmapCore.BC_READ_IGNORE_COLOR_PROFILE, 38 | } 39 | 40 | /// 41 | /// Flags for customizing behavior when translating a Bitmap implementation into data. 42 | /// 43 | [Flags] 44 | public enum BitmapWriterFlags : uint 45 | { 46 | /// 47 | /// No special writer flags 48 | /// 49 | None = 0, 50 | 51 | /// 52 | /// This specifies that the bitmap must be created with a BITMAPV5HEADER. This is desirable if storing the image to the cliboard at CF_DIBV5 for example. 53 | /// 54 | ForceV5Header = BitmapCore.BC_WRITE_V5, 55 | 56 | /// 57 | /// This specifies that the bitmap must be created with a BITMAPINFOHEADER. This is required when storing the image to the clipboard at CF_DIB, or possibly 58 | /// for interoping with other applications that do not support newer bitmap files. This option is not advisable unless absolutely required - as not all bitmaps 59 | /// can be accurately represented. For example, no transparency data can be stored - and the images will appear fully opaque. 60 | /// 61 | ForceInfoHeader = BitmapCore.BC_WRITE_VINFO, 62 | 63 | /// 64 | /// This option requests that the bitmap be created without a BITMAPFILEHEADER (ie, in Packed DIB format). This is used when storing the file to the clipboard. 65 | /// 66 | SkipFileHeader = BitmapCore.BC_WRITE_SKIP_FH, 67 | } 68 | 69 | /// 70 | /// Interface for converting bitmap streams into a concrete Bitmap implementation class, 71 | /// usually provided by your UI framework. 72 | /// 73 | /// The Bitmap implementation class 74 | public interface IBitmapConverter 75 | { 76 | /// 77 | /// Read a bitmap from a data stream (such as a web request, or a filestream). 78 | /// 79 | public TBitmap Read(Stream stream); 80 | 81 | /// 82 | /// Read a bitmap from a data stream (such as a web request, or a filestream). 83 | /// 84 | public TBitmap Read(Stream stream, BitmapReaderFlags pFlags); 85 | 86 | /// 87 | /// Read a bitmap from a byte array. 88 | /// 89 | public TBitmap Read(byte[] data); 90 | 91 | /// 92 | /// Read a bitmap from a byte array. 93 | /// 94 | public TBitmap Read(byte[] data, BitmapReaderFlags pFlags); 95 | 96 | /// 97 | /// Read a bitmap from an unsafe pointer. 98 | /// 99 | public unsafe TBitmap Read(byte* data, int dataLength, BitmapReaderFlags rFlags); 100 | 101 | /// 102 | /// Write a bitmap to a byte array. 103 | /// 104 | public byte[] GetBytes(TBitmap bitmap); 105 | 106 | /// 107 | /// Write a bitmap to a byte array. 108 | /// 109 | public byte[] GetBytes(TBitmap bitmap, BitmapWriterFlags wFlags); 110 | } 111 | 112 | /// 113 | /// Base class for converter implementations 114 | /// 115 | public abstract class BitmapConverterBase : IBitmapConverter 116 | { 117 | /// 118 | public byte[] GetBytes(TBitmap bitmap) => GetBytes(bitmap, BitmapWriterFlags.None); 119 | 120 | /// 121 | public abstract byte[] GetBytes(TBitmap bitmap, BitmapWriterFlags wFlags); 122 | 123 | /// 124 | public TBitmap Read(Stream stream) => Read(StructUtil.ReadBytes(stream)); 125 | 126 | /// 127 | public TBitmap Read(Stream stream, BitmapReaderFlags pFlags) => Read(StructUtil.ReadBytes(stream), pFlags); 128 | 129 | /// 130 | public TBitmap Read(byte[] data) => Read(data, BitmapReaderFlags.None); 131 | 132 | /// 133 | public unsafe TBitmap Read(byte[] data, BitmapReaderFlags pFlags) 134 | { 135 | fixed (byte* ptr = data) 136 | return Read(ptr, data.Length, pFlags); 137 | } 138 | 139 | /// 140 | public abstract unsafe TBitmap Read(byte* data, int dataLength, BitmapReaderFlags rFlags); 141 | } 142 | 143 | /// 144 | /// Base class for converter implementations providing static helper methods 145 | /// 146 | public abstract class BitmapConverterStaticBase : BitmapConverterBase 147 | where TSelf : BitmapConverterStaticBase, new() 148 | { 149 | /// 150 | /// Read a bitmap from a data stream (such as a web request, or a filestream). 151 | /// 152 | public static TBitmap FromStream(Stream stream) 153 | { 154 | var impl = new TSelf(); 155 | return impl.Read(stream); 156 | } 157 | 158 | /// 159 | /// Read a bitmap from a data stream (such as a web request, or a filestream). 160 | /// 161 | public static TBitmap FromStream(Stream stream, BitmapReaderFlags pFlags) 162 | { 163 | var impl = new TSelf(); 164 | return impl.Read(stream, pFlags); 165 | } 166 | 167 | /// 168 | /// Read a bitmap from a byte array. 169 | /// 170 | public static TBitmap FromBytes(byte[] data) 171 | { 172 | var impl = new TSelf(); 173 | return impl.Read(data); 174 | } 175 | 176 | /// 177 | /// Read a bitmap from a byte array. 178 | /// 179 | public static TBitmap FromBytes(byte[] data, BitmapReaderFlags pFlags) 180 | { 181 | var impl = new TSelf(); 182 | return impl.Read(data, pFlags); 183 | } 184 | 185 | /// 186 | /// Write a bitmap to a byte array. 187 | /// 188 | public static byte[] ToBytes(TBitmap bitmap) 189 | { 190 | var impl = new TSelf(); 191 | return impl.GetBytes(bitmap); 192 | } 193 | 194 | /// 195 | /// Write a bitmap to a byte array. 196 | /// 197 | public static byte[] ToBytes(TBitmap bitmap, BitmapWriterFlags wFlags) 198 | { 199 | var impl = new TSelf(); 200 | return impl.GetBytes(bitmap, wFlags); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Bitmaps/PointerStream.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Clowd.Clipboard.Bitmaps; 5 | 6 | public unsafe class PointerStream : Stream 7 | { 8 | private readonly byte* _bufferStart; 9 | private readonly long _bufferLen; 10 | private long _position; 11 | 12 | public override bool CanRead => true; 13 | 14 | public override bool CanSeek => true; 15 | 16 | public override bool CanWrite => false; 17 | 18 | public override long Length => _bufferLen; 19 | 20 | public override long Position 21 | { 22 | get => _position; 23 | set => _position = value; 24 | } 25 | 26 | public PointerStream(byte* buffer, long bufferLen) 27 | { 28 | _bufferStart = buffer; 29 | _bufferLen = bufferLen; 30 | } 31 | 32 | public override void Flush() 33 | { 34 | // nop 35 | } 36 | 37 | public override int Read(byte[] buffer, int offset, int count) 38 | { 39 | count = Math.Min(count, (int)(_bufferLen - _position)); 40 | Marshal.Copy((IntPtr)(_bufferStart + _position), buffer, offset, count); 41 | _position += count; 42 | return count; 43 | } 44 | 45 | public override long Seek(long offset, SeekOrigin origin) 46 | { 47 | switch (origin) 48 | { 49 | case SeekOrigin.Begin: 50 | _position = offset; 51 | return _position; 52 | case SeekOrigin.Current: 53 | _position += offset; 54 | return _position; 55 | case SeekOrigin.End: 56 | _position = _bufferLen + offset; 57 | return _position; 58 | default: 59 | throw new ArgumentOutOfRangeException(nameof(origin)); 60 | } 61 | } 62 | 63 | public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); 64 | 65 | public override void SetLength(long value) => throw new NotSupportedException(); 66 | } 67 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Bitmaps/StructUtil.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Clowd.Clipboard.Bitmaps; 5 | 6 | public unsafe class StructUtil 7 | { 8 | public static T Deserialize(byte* ptr) 9 | { 10 | return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T)); 11 | } 12 | 13 | public static void SerializeTo(T s, byte[] buffer, ref uint destOffset) where T : struct 14 | { 15 | var size = Marshal.SizeOf(typeof(T)); 16 | var ptr = Marshal.AllocHGlobal(size); 17 | Marshal.StructureToPtr(s, ptr, true); 18 | Marshal.Copy(ptr, buffer, (int)destOffset, size); 19 | Marshal.FreeHGlobal(ptr); 20 | destOffset += (uint)size; 21 | } 22 | 23 | public static ushort ReadU16(byte* ptr) 24 | { 25 | var arr = new byte[] { *ptr, *(ptr + 1) }; 26 | return BitConverter.ToUInt16(arr, 0); 27 | } 28 | 29 | public static uint ReadU24(byte* ptr) 30 | { 31 | var arr = new byte[] { *ptr, *(ptr + 1), *(ptr + 2) }; 32 | return BitConverter.ToUInt32(arr, 0); 33 | } 34 | 35 | public static uint ReadU32(byte* ptr) 36 | { 37 | var arr = new byte[] { *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3) }; 38 | return BitConverter.ToUInt32(arr, 0); 39 | } 40 | 41 | public static byte[] ReadBytes(Stream stream) 42 | { 43 | if (stream is MemoryStream mem) 44 | { 45 | return mem.ToArray(); 46 | } 47 | else 48 | { 49 | byte[] buffer = new byte[4096]; 50 | using (MemoryStream ms = new MemoryStream()) 51 | { 52 | while (true) 53 | { 54 | int read = stream.Read(buffer, 0, buffer.Length); 55 | if (read <= 0) 56 | return ms.ToArray(); 57 | ms.Write(buffer, 0, read); 58 | } 59 | } 60 | } 61 | } 62 | 63 | public static int CalcShift(uint mask) 64 | { 65 | for (int shift = 0; shift < sizeof(uint) * 8; ++shift) 66 | { 67 | if ((mask & 1 << shift) != 0) 68 | { 69 | return shift; 70 | } 71 | } 72 | throw new NotSupportedException("Invalid Bitmask"); 73 | } 74 | 75 | public static uint CalcStride(ushort bbp, int width) => (bbp * (uint)width + 31) / 32 * 4; 76 | } 77 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/ClipboardBusyException.cs: -------------------------------------------------------------------------------- 1 | namespace Clowd.Clipboard; 2 | 3 | /// 4 | /// Thrown when the clipboard can not be opened for reading or writing because it is locked by another thread or application. 5 | /// 6 | public class ClipboardBusyException : Exception 7 | { 8 | /// 9 | /// The Id of the process currently locking the clipboard. 10 | /// 11 | public int ProcessId { get; } 12 | 13 | /// 14 | /// The Name of the process currently locking the clipboard 15 | /// 16 | public string ProcessName { get; } 17 | 18 | /// 19 | /// Create a new ClipboardBusyException 20 | /// 21 | public ClipboardBusyException() : base("Failed to open clipboard. Try again later.") 22 | { 23 | 24 | } 25 | 26 | /// 27 | /// Create a new ClipboardBusyException with an inner exception 28 | /// 29 | public ClipboardBusyException(Exception inner) : base("Failed to open clipboard. Try again later.", inner) 30 | { 31 | 32 | } 33 | 34 | /// 35 | /// Create a new ClipboardBusyException while also which process is currently locking the clipboard. 36 | /// 37 | public ClipboardBusyException(int processId, string processName) : base($"Failed to open clipboard. It is currently locked by '{processName}' (pid.{processId}).") 38 | { 39 | ProcessId = processId; 40 | ProcessName = processName; 41 | } 42 | 43 | /// 44 | /// Create a new ClipboardBusyException while also which process is currently locking the clipboard and providing an inner exception. 45 | /// 46 | public ClipboardBusyException(int processId, string processName, Exception inner) : base($"Failed to open clipboard. It is currently locked by '{processName}' (pid.{processId}).", inner) 47 | { 48 | ProcessId = processId; 49 | ProcessName = processName; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/ClipboardHandlePlatformBase.cs: -------------------------------------------------------------------------------- 1 | using Clowd.Clipboard.Formats; 2 | 3 | namespace Clowd.Clipboard; 4 | 5 | /// 6 | /// Provides the platform-specific clipboard methods (such as retrieving and setting images). 7 | /// 8 | public interface IClipboardHandlePlatform 9 | { 10 | /// 11 | /// Check if the clipboard currently contains any known image format. 12 | /// 13 | bool ContainsImage(); 14 | 15 | /// 16 | /// Retrieves any detectable bitmap stored on the clipboard. 17 | /// 18 | TBitmap GetImage(); 19 | 20 | /// 21 | /// Sets the image on the clipboard to the specified bitmap. 22 | /// 23 | void SetImage(TBitmap bitmap); 24 | } 25 | 26 | /// 27 | /// Represents a handle to the clipboard. Open the handle via , read or 28 | /// set the clipboard, and then dispose this class as quickly as possible. Leaving this handle 29 | /// open for too long will prevent other applications from accessing the clipboard, and may 30 | /// even cause them to freeze for a time. 31 | /// 32 | [SupportedOSPlatform("windows")] 33 | public abstract class ClipboardHandlePlatformBase : ClipboardHandle 34 | where TBitmap : class 35 | { 36 | private List> _prioritisedFormats = new(); 37 | private List> _clipboardOrderFormats = new(); 38 | private List> _otherFormats = new(); 39 | 40 | private IEnumerable> _allImageFormats 41 | { 42 | get 43 | { 44 | foreach (var b in _prioritisedFormats) yield return b; 45 | foreach (var b in _clipboardOrderFormats) yield return b; 46 | foreach (var b in _otherFormats) yield return b; 47 | } 48 | } 49 | 50 | /// 51 | /// Creates a new . 52 | /// 53 | protected ClipboardHandlePlatformBase() 54 | { 55 | var jpeg = GetJpegConverter(); 56 | var png = GetPngConverter(); 57 | var tiff = GetTiffConverter(); 58 | var gif = GetGifConverter(); 59 | 60 | _prioritisedFormats.Add(ClipboardFormat.Png.WithConverter(png)); 61 | _prioritisedFormats.Add(ClipboardFormat.CreateCustomFormat("image/png", png)); 62 | 63 | _clipboardOrderFormats.Add(ClipboardFormat.Dib.WithConverter(GetDibConverter())); 64 | _clipboardOrderFormats.Add(ClipboardFormat.DibV5.WithConverter(GetDibV5Converter())); 65 | _clipboardOrderFormats.Add(ClipboardFormat.Bitmap.WithConverter(GetGdiHandleConverter())); 66 | 67 | _otherFormats.Add(ClipboardFormat.Jpeg.WithConverter(jpeg)); 68 | _otherFormats.Add(ClipboardFormat.CreateCustomFormat("JPG", jpeg)); 69 | _otherFormats.Add(ClipboardFormat.CreateCustomFormat("JFIF", jpeg)); 70 | _otherFormats.Add(ClipboardFormat.CreateCustomFormat("image/jpeg", jpeg)); 71 | 72 | _otherFormats.Add(ClipboardFormat.Tiff.WithConverter(tiff)); 73 | _otherFormats.Add(ClipboardFormat.CreateCustomFormat("image/tiff", tiff)); 74 | 75 | _otherFormats.Add(ClipboardFormat.Gif.WithConverter(gif)); 76 | _otherFormats.Add(ClipboardFormat.CreateCustomFormat("image/gif", gif)); 77 | } 78 | 79 | 80 | /// 81 | /// Check if the clipboard currently contains any known image format. 82 | /// 83 | public virtual bool ContainsImage() => ContainsFormat(_allImageFormats.ToArray()) || TryGetFileDropImagePath(out _); 84 | 85 | /// 86 | /// Get a list of known image file extensions. 87 | /// 88 | protected virtual string[] KnownImageExtensions => new[] 89 | { 90 | ".png", ".jpg", ".jpeg",".jpe", ".jfif", 91 | ".bmp", ".gif", ".tif", ".tiff", ".ico", 92 | }; 93 | 94 | /// 95 | /// Returns true if there is only a single file in the file drop list, and that file has an image extension. 96 | /// 97 | protected virtual bool TryGetFileDropImagePath(out string filePath) 98 | { 99 | filePath = null; 100 | 101 | if (!ContainsFileDropList()) 102 | return false; 103 | 104 | var fileDropList = GetFileDropList(); 105 | if (fileDropList != null && fileDropList.Length == 1) 106 | { 107 | var f = fileDropList[0]; 108 | if (File.Exists(f) && KnownImageExtensions.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) 109 | { 110 | filePath = f; 111 | return true; 112 | } 113 | } 114 | 115 | return false; 116 | } 117 | 118 | /// 119 | /// Sets the image on the clipboard to the specified bitmap. 120 | /// 121 | protected virtual void SetImageImpl(TBitmap bitmap) 122 | { 123 | var pngFormat = _allImageFormats.FirstOrDefault(f => f == ClipboardFormat.Png); 124 | if (pngFormat?.TypeObjectReader != null) 125 | SetFormat(pngFormat, bitmap); 126 | 127 | var dibFormat = _allImageFormats.FirstOrDefault(f => f == ClipboardFormat.Dib); 128 | if (dibFormat?.TypeObjectReader != null) 129 | SetFormat(dibFormat, bitmap); 130 | } 131 | 132 | /// 133 | /// Retrieves any detectable bitmap stored on the clipboard. 134 | /// 135 | protected virtual TBitmap GetImageImpl() 136 | { 137 | var formats = GetPresentFormats().ToArray(); 138 | 139 | // first we search for prioritised formats (like PNG) 140 | foreach (var f in _prioritisedFormats) 141 | if (formats.Contains(f)) 142 | if (TryGetFormatObject(f.Id, f.TypeObjectReader, out var bitmap)) 143 | return bitmap; 144 | 145 | // Windows has "Synthesized Formats", if you ask for a CF_DIBV5 when there is only a CF_DIB, it will transparently convert 146 | // from one format to the other. The issue is, if you ask for a CF_DIBV5 before you ask for a CF_DIB, and the CF_DIB is 147 | // the only real format on the clipboard, windows can corrupt the CF_DIB!!! 148 | // One quirk is that windows deterministically puts real formats in the list of present formats before it puts synthesized formats 149 | // so even though we can't really tell what is synthesized or not, we can make a guess based on which comes first. 150 | foreach (var fmt in formats) 151 | { 152 | var orderedFormat = _clipboardOrderFormats.FirstOrDefault(f => f == fmt); 153 | if (orderedFormat?.TypeObjectReader != null) 154 | { 155 | if (TryGetFormatObject(orderedFormat.Id, orderedFormat.TypeObjectReader, out var dib)) 156 | { 157 | if (dib != null) 158 | { 159 | return dib; 160 | } 161 | } 162 | } 163 | } 164 | 165 | // now we search "other" formats (like JPEG) 166 | foreach (var f in _otherFormats) 167 | if (formats.Contains(f)) 168 | if (TryGetFormatObject(f.Id, f.TypeObjectReader, out var bitmap)) 169 | return bitmap; 170 | 171 | // check the windows file drop list to see if someone copied an image from explorer. 172 | if (TryGetFileDropImagePath(out var fileDropImagePath)) 173 | return LoadFromFile(fileDropImagePath); 174 | 175 | return null; 176 | } 177 | 178 | /// Load's a bitmap from a local file path. 179 | protected abstract TBitmap LoadFromFile(string filePath); 180 | 181 | /// Get specified image converter. 182 | protected abstract IDataConverter GetJpegConverter(); 183 | 184 | /// Get specified image converter. 185 | protected abstract IDataConverter GetTiffConverter(); 186 | 187 | /// Get specified image converter. 188 | protected abstract IDataConverter GetGifConverter(); 189 | 190 | /// Get specified image converter. 191 | protected abstract IDataConverter GetPngConverter(); 192 | 193 | /// Get specified image converter. 194 | protected abstract IDataConverter GetGdiHandleConverter(); 195 | 196 | /// Get specified image converter. 197 | protected abstract IDataConverter GetDibConverter(); 198 | 199 | /// Get specified image converter. 200 | protected abstract IDataConverter GetDibV5Converter(); 201 | } 202 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/ClipboardStaticBase.cs: -------------------------------------------------------------------------------- 1 | namespace Clowd.Clipboard; 2 | 3 | /// 4 | /// The base class for creating a static clipboard helper. 5 | /// 6 | [SupportedOSPlatform("windows")] 7 | public class ClipboardStaticBase 8 | where THandle : ClipboardHandle, IClipboardHandlePlatform, new() 9 | where TBitmap : class 10 | { 11 | /// 12 | /// Do not use. 13 | /// 14 | protected ClipboardStaticBase() 15 | { 16 | } 17 | 18 | /// 19 | /// Opens a clipboard handle. You are responsible for disposing of it when done. 20 | /// 21 | public static THandle Open() 22 | { 23 | var ch = new THandle(); 24 | ch.Open(); 25 | return ch; 26 | } 27 | 28 | /// 29 | /// Opens a clipboard handle. You are responsible for disposing of it when done. 30 | /// 31 | public static async Task OpenAsync() 32 | { 33 | var ch = new THandle(); 34 | await ch.OpenAsync().ConfigureAwait(false); 35 | return ch; 36 | } 37 | 38 | /// 39 | public static void Empty() 40 | { 41 | using var ch = Open(); 42 | ch.Empty(); 43 | } 44 | 45 | /// 46 | public static async Task EmptyAsync() 47 | { 48 | using var ch = await OpenAsync().ConfigureAwait(false); 49 | ch.Empty(); 50 | } 51 | 52 | /// 53 | public static string GetText() 54 | { 55 | using var ch = Open(); 56 | return ch.GetText(); 57 | } 58 | 59 | /// 60 | public static async Task GetTextAsync() 61 | { 62 | using var ch = await OpenAsync().ConfigureAwait(false); 63 | return ch.GetText(); 64 | } 65 | 66 | /// 67 | public static void SetText(string text) 68 | { 69 | using var ch = Open(); 70 | ch.SetText(text); 71 | } 72 | 73 | /// 74 | public static async Task SetTextAsync(string text) 75 | { 76 | using var ch = await OpenAsync().ConfigureAwait(false); 77 | ch.SetText(text); 78 | } 79 | 80 | /// 81 | public static void SetImage(TBitmap bitmap) 82 | { 83 | using var ch = Open(); 84 | ch.SetImage(bitmap); 85 | } 86 | 87 | /// 88 | public static async Task SetImageAsync(TBitmap bitmap) 89 | { 90 | using var ch = await OpenAsync().ConfigureAwait(false); 91 | ch.SetImage(bitmap); 92 | } 93 | 94 | /// 95 | public static TBitmap GetImage() 96 | { 97 | using var ch = Open(); 98 | return ch.GetImage(); 99 | } 100 | 101 | /// 102 | public static async Task GetImageAsync() 103 | { 104 | using var ch = await OpenAsync().ConfigureAwait(false); 105 | return ch.GetImage(); 106 | } 107 | 108 | /// 109 | public static string[] GetFileDropList() 110 | { 111 | using var ch = Open(); 112 | return ch.GetFileDropList(); 113 | } 114 | 115 | /// 116 | public static async Task GetFileDropListAsync() 117 | { 118 | using var ch = await OpenAsync().ConfigureAwait(false); 119 | return ch.GetFileDropList(); 120 | } 121 | 122 | /// 123 | public static void SetFileDropList(string[] files) 124 | { 125 | using var ch = Open(); 126 | ch.SetFileDropList(files); 127 | } 128 | 129 | /// 130 | public static async Task SetFileDropListAsync(string[] files) 131 | { 132 | using var ch = await OpenAsync().ConfigureAwait(false); 133 | ch.SetFileDropList(files); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/ClipboardWindow.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Clowd.Clipboard; 4 | 5 | /// 6 | /// Represents a static HWND window responsible for owning the clipboard when it is open. 7 | /// 8 | [SupportedOSPlatform("windows")] 9 | public static class ClipboardWindow 10 | { 11 | /// 12 | /// The window handle. 13 | /// 14 | public static IntPtr Handle => _hWindow; 15 | 16 | static readonly WindowProcedureHandler _wndProc; 17 | static readonly IntPtr _hWindow; 18 | static readonly short _clsAtom; 19 | static readonly string _clsName; 20 | 21 | static ClipboardWindow() 22 | { 23 | _wndProc = NativeMethods.DefWindowProc; 24 | _clsName = "ClowdClipboardLib_" + DateTime.Now.Ticks; 25 | 26 | WindowClass wc; 27 | wc.style = 0; 28 | wc.lpfnWndProc = _wndProc; 29 | wc.cbClsExtra = 0; 30 | wc.cbWndExtra = 0; 31 | wc.hInstance = IntPtr.Zero; 32 | wc.hIcon = IntPtr.Zero; 33 | wc.hCursor = IntPtr.Zero; 34 | wc.hbrBackground = IntPtr.Zero; 35 | wc.lpszMenuName = ""; 36 | wc.lpszClassName = _clsName; 37 | 38 | // we just create one window for this process, it will be used for all future clipboard handles. 39 | // this class will be unregistered when the process exits. 40 | _clsAtom = NativeMethods.RegisterClass(ref wc); 41 | if (_clsAtom == 0) 42 | throw new Win32Exception(); 43 | 44 | _hWindow = NativeMethods.CreateWindowEx(0, _clsName, "", 0, 0, 0, 1, 1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); 45 | if (_hWindow == IntPtr.Zero) 46 | throw new Win32Exception(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Clowd.Clipboard.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461;netstandard2.0;net6.0;net8.0 5 | true 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Formats/FileDropConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text; 3 | 4 | namespace Clowd.Clipboard.Formats; 5 | 6 | /// 7 | /// Converter for native windows file drop lists containing a list of file paths. 8 | /// 9 | [SupportedOSPlatform("windows")] 10 | public class FileDropConverter : HandleDataConverterBase 11 | { 12 | const int PATH_MAX_LEN = 260; 13 | const int PATH_LONG_MAX_LEN = short.MaxValue; 14 | const int baseStructSize = 4 + 8 + 4 + 4; 15 | 16 | /// 17 | public override string[] ReadFromHandle(IntPtr hdrop, int memSize) 18 | { 19 | string[] files = null; 20 | StringBuilder sb = new StringBuilder(PATH_MAX_LEN); 21 | 22 | int count = NativeMethods.DragQueryFile(hdrop, unchecked((int)0xFFFFFFFF), null, 0); 23 | if (count > 0) 24 | { 25 | files = new string[count]; 26 | 27 | for (int i = 0; i < count; i++) 28 | { 29 | int charlen = DragQueryFileLongPath(hdrop, i, sb); 30 | if (0 == charlen) 31 | continue; 32 | 33 | files[i] = sb.ToString(0, charlen); 34 | } 35 | } 36 | 37 | return files; 38 | } 39 | 40 | private static int DragQueryFileLongPath(IntPtr hDrop, int iFile, StringBuilder lpszFile) 41 | { 42 | if (null != lpszFile && 0 != lpszFile.Capacity && iFile != unchecked((int)0xFFFFFFFF)) 43 | { 44 | int resultValue = 0; 45 | 46 | // iterating by allocating chunk of memory each time we find the length is not sufficient. 47 | // Performance should not be an issue for current MAX_PATH length due to this 48 | if ((resultValue = NativeMethods.DragQueryFile(hDrop, iFile, lpszFile, lpszFile.Capacity)) == lpszFile.Capacity) 49 | { 50 | // passing null for buffer will return actual number of charectors in the file name. 51 | // So, one extra call would be suffice to avoid while loop in case of long path. 52 | int capacity = NativeMethods.DragQueryFile(hDrop, iFile, null, 0); 53 | if (capacity < PATH_LONG_MAX_LEN) 54 | { 55 | lpszFile.EnsureCapacity(capacity); 56 | resultValue = NativeMethods.DragQueryFile(hDrop, iFile, lpszFile, capacity); 57 | } 58 | else 59 | { 60 | resultValue = 0; 61 | } 62 | } 63 | 64 | lpszFile.Length = resultValue; 65 | return resultValue; // what ever the result. 66 | } 67 | else 68 | { 69 | return NativeMethods.DragQueryFile(hDrop, iFile, lpszFile, lpszFile.Capacity); 70 | } 71 | } 72 | 73 | /// 74 | public override int GetDataSize(string[] files) 75 | { 76 | bool unicode = (Marshal.SystemDefaultCharSize != 1); 77 | int sizeInBytes = baseStructSize; 78 | 79 | if (unicode) 80 | { 81 | for (int i = 0; i < files.Length; i++) 82 | { 83 | sizeInBytes += (files[i].Length + 1) * 2; 84 | } 85 | sizeInBytes += 2; 86 | } 87 | else 88 | { 89 | for (int i = 0; i < files.Length; i++) 90 | { 91 | sizeInBytes += GetPInvokeStringLength(files[i]) + 1; 92 | } 93 | sizeInBytes++; 94 | } 95 | return sizeInBytes; 96 | } 97 | 98 | int GetPInvokeStringLength(String s) 99 | { 100 | if (s == null) 101 | { 102 | return 0; 103 | } 104 | 105 | if (Marshal.SystemDefaultCharSize == 2) 106 | { 107 | return s.Length; 108 | } 109 | else 110 | { 111 | if (s.Length == 0) 112 | { 113 | return 0; 114 | } 115 | if (s.IndexOf('\0') > -1) 116 | { 117 | return GetEmbeddedNullStringLengthAnsi(s); 118 | } 119 | else 120 | { 121 | return NativeMethods.lstrlen(s); 122 | } 123 | } 124 | } 125 | 126 | int GetEmbeddedNullStringLengthAnsi(String s) 127 | { 128 | int n = s.IndexOf('\0'); 129 | if (n > -1) 130 | { 131 | String left = s.Substring(0, n); 132 | String right = s.Substring(n + 1); 133 | return GetPInvokeStringLength(left) + GetEmbeddedNullStringLengthAnsi(right) + 1; 134 | } 135 | else 136 | { 137 | return GetPInvokeStringLength(s); 138 | } 139 | } 140 | 141 | /// 142 | public override void WriteToHandle(string[] files, IntPtr currentPtr) 143 | { 144 | //if (files == null) 145 | //{ 146 | // return NativeMethods.S_OK; 147 | //} 148 | //else if (files.Length < 1) 149 | //{ 150 | // return NativeMethods.S_OK; 151 | //} 152 | //if (handle == IntPtr.Zero) 153 | //{ 154 | // return (NativeMethods.E_INVALIDARG); 155 | //} 156 | 157 | 158 | bool unicode = (Marshal.SystemDefaultCharSize != 1); 159 | 160 | //IntPtr currentPtr = IntPtr.Zero; 161 | //int sizeInBytes = baseStructSize; 162 | 163 | //// First determine the size of the array 164 | //if (unicode) 165 | //{ 166 | // for (int i = 0; i < files.Length; i++) 167 | // { 168 | // sizeInBytes += (files[i].Length + 1) * 2; 169 | // } 170 | // sizeInBytes += 2; 171 | //} 172 | //else 173 | //{ 174 | // for (int i = 0; i < files.Length; i++) 175 | // { 176 | // sizeInBytes += GetPInvokeStringLength(files[i]) + 1; 177 | // } 178 | // sizeInBytes++; 179 | //} 180 | 181 | //// Alloc the Win32 memory 182 | //IntPtr newHandle = UnsafeNativeMethods.GlobalReAlloc(new HandleRef(null, handle), 183 | // sizeInBytes, 184 | // NativeMethods.GMEM_MOVEABLE | NativeMethods.GMEM_DDESHARE); 185 | //if (newHandle == IntPtr.Zero) 186 | //{ 187 | // return (NativeMethods.E_OUTOFMEMORY); 188 | //} 189 | //IntPtr basePtr = UnsafeNativeMethods.GlobalLock(new HandleRef(null, newHandle)); 190 | //if (basePtr == IntPtr.Zero) 191 | //{ 192 | // return (NativeMethods.E_OUTOFMEMORY); 193 | //} 194 | //currentPtr = basePtr; 195 | 196 | // Write out the struct... 197 | // 198 | int[] structData = new int[] { baseStructSize, 0, 0, 0, 0 }; 199 | 200 | if (unicode) 201 | { 202 | structData[4] = unchecked((int)0xFFFFFFFF); 203 | } 204 | 205 | Marshal.Copy(structData, 0, currentPtr, structData.Length); 206 | currentPtr = (IntPtr)((long)currentPtr + baseStructSize); 207 | 208 | for (int i = 0; i < files.Length; i++) 209 | { 210 | if (unicode) 211 | { 212 | 213 | NativeMethods.CopyMemoryW(currentPtr, files[i].ToCharArray(), files[i].Length * 2); 214 | currentPtr = (IntPtr)((long)currentPtr + (files[i].Length * 2)); 215 | Marshal.Copy(new byte[] { 0, 0 }, 0, currentPtr, 2); 216 | currentPtr = (IntPtr)((long)currentPtr + 2); 217 | } 218 | else 219 | { 220 | int pinvokeLen = GetPInvokeStringLength(files[i]); 221 | NativeMethods.CopyMemoryA(currentPtr, files[i].ToCharArray(), pinvokeLen); 222 | currentPtr = (IntPtr)((long)currentPtr + pinvokeLen); 223 | Marshal.Copy(new byte[] { 0 }, 0, currentPtr, 1); 224 | currentPtr = (IntPtr)((long)currentPtr + 1); 225 | } 226 | } 227 | 228 | if (unicode) 229 | { 230 | Marshal.Copy(new char[] { '\0' }, 0, currentPtr, 1); 231 | //currentPtr = (IntPtr)((long)currentPtr + 2); 232 | } 233 | else 234 | { 235 | Marshal.Copy(new byte[] { 0 }, 0, currentPtr, 1); 236 | //currentPtr = (IntPtr)((long)currentPtr + 1); 237 | } 238 | 239 | //UnsafeNativeMethods.GlobalUnlock(new HandleRef(null, newHandle)); 240 | //return NativeMethods.S_OK; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Formats/IDataConverter.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Clowd.Clipboard.Formats; 5 | 6 | /// 7 | /// Basic interface for converting an object to/from a handle (HGlobal) that can be stored on the clipboard. 8 | /// 9 | public interface IDataConverter 10 | { 11 | /// 12 | /// Write's the specified managed object into a handle (HGlobal) suitable for storage on the clipboard. 13 | /// 14 | IntPtr WriteToHGlobal(T obj); 15 | 16 | /// 17 | /// Reads the data at the specified handle (HGlobal) into a managed object. 18 | /// 19 | T ReadFromHGlobal(IntPtr hGlobal); 20 | } 21 | 22 | /// 23 | /// A class which reads the underlying HGlobal into an array of bytes. 24 | /// 25 | [SupportedOSPlatform("windows")] 26 | public class BytesDataConverter : BytesDataConverterBase 27 | { 28 | /// 29 | public override byte[] ReadFromBytes(byte[] data) => data; 30 | 31 | /// 32 | public override byte[] WriteToBytes(byte[] obj) => obj; 33 | } 34 | 35 | /// 36 | /// A base class which reads the underlying HGlobal into an array of bytes for further processing. 37 | /// 38 | [SupportedOSPlatform("windows")] 39 | public abstract class BytesDataConverterBase : IDataConverter 40 | { 41 | /// 42 | /// Writes the specified managed object into an array of bytes. 43 | /// 44 | public abstract byte[] WriteToBytes(T obj); 45 | 46 | /// 47 | /// Creates a new managed object by reading the array of bytes. 48 | /// 49 | public abstract T ReadFromBytes(byte[] data); 50 | 51 | /// 52 | public virtual IntPtr WriteToHGlobal(T obj) 53 | { 54 | var bytes = WriteToBytes(obj); 55 | var size = bytes.Length; 56 | 57 | var hglobal = NativeMethods.GlobalAlloc(NativeMethods.GMEM_MOVEABLE | NativeMethods.GMEM_ZEROINIT, size); 58 | if (hglobal == IntPtr.Zero) 59 | throw new Win32Exception(); 60 | 61 | var ptr = NativeMethods.GlobalLock(hglobal); 62 | if (ptr == IntPtr.Zero) 63 | throw new Win32Exception(); 64 | 65 | try 66 | { 67 | Marshal.Copy(bytes, 0, ptr, size); 68 | } 69 | finally 70 | { 71 | NativeMethods.GlobalUnlock(hglobal); 72 | } 73 | 74 | return hglobal; 75 | } 76 | 77 | /// 78 | public virtual T ReadFromHGlobal(IntPtr hglobal) 79 | { 80 | var ptr = NativeMethods.GlobalLock(hglobal); 81 | if (ptr == IntPtr.Zero) 82 | throw new Win32Exception(); 83 | 84 | try 85 | { 86 | var size = NativeMethods.GlobalSize(hglobal); 87 | byte[] bytes = new byte[size]; 88 | Marshal.Copy(ptr, bytes, 0, size); 89 | return ReadFromBytes(bytes); 90 | } 91 | finally 92 | { 93 | NativeMethods.GlobalUnlock(hglobal); 94 | } 95 | } 96 | } 97 | 98 | /// 99 | /// A base class which locks an HGlobal to retrieve the underlying data pointer for further processing 100 | /// 101 | [SupportedOSPlatform("windows")] 102 | public abstract class HandleDataConverterBase : IDataConverter 103 | { 104 | /// 105 | /// Gets the size of the object in bytes. This function is called before 106 | /// when determining how much memory to allocate. 107 | /// 108 | public abstract int GetDataSize(T obj); 109 | 110 | /// 111 | /// Writes the object to the specified unmanaged pointer. 112 | /// 113 | /// 114 | /// 115 | public abstract void WriteToHandle(T obj, IntPtr ptr); 116 | 117 | /// 118 | /// Reads the data at the specified pointer. 119 | /// 120 | /// The unmanaged pointer. 121 | /// The size of the data stored at the pointer. 122 | /// The parsed object 123 | public abstract T ReadFromHandle(IntPtr ptr, int memSize); 124 | 125 | /// 126 | public virtual T ReadFromHGlobal(IntPtr hglobal) 127 | { 128 | var ptr = NativeMethods.GlobalLock(hglobal); 129 | if (ptr == IntPtr.Zero) 130 | throw new Win32Exception(); 131 | 132 | try 133 | { 134 | var size = NativeMethods.GlobalSize(hglobal); 135 | return ReadFromHandle(ptr, size); 136 | } 137 | finally 138 | { 139 | NativeMethods.GlobalUnlock(hglobal); 140 | } 141 | } 142 | 143 | /// 144 | public virtual IntPtr WriteToHGlobal(T obj) 145 | { 146 | var size = GetDataSize(obj); 147 | 148 | var hglobal = NativeMethods.GlobalAlloc(NativeMethods.GMEM_MOVEABLE | NativeMethods.GMEM_ZEROINIT, size); 149 | if (hglobal == IntPtr.Zero) 150 | throw new Win32Exception(); 151 | 152 | var ptr = NativeMethods.GlobalLock(hglobal); 153 | if (ptr == IntPtr.Zero) 154 | throw new Win32Exception(); 155 | 156 | try 157 | { 158 | WriteToHandle(obj, ptr); 159 | } 160 | finally 161 | { 162 | NativeMethods.GlobalUnlock(hglobal); 163 | } 164 | 165 | return hglobal; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Formats/Int32Converter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Clowd.Clipboard.Formats; 5 | 6 | /// 7 | /// Base class to store and read 32-bit integers on the clipboard and convert them to a managed type (eg. enums). 8 | /// 9 | public abstract class Int32DataConverterBase : HandleDataConverterBase 10 | { 11 | /// 12 | /// Reads the specified integer and converts it into the target managed object. 13 | /// 14 | public abstract T ReadFromInt32(int val); 15 | 16 | /// 17 | /// Convert the specified managed object into it's integer representation. 18 | /// 19 | public abstract int WriteToInt32(T obj); 20 | 21 | /// 22 | public override int GetDataSize(T obj) => sizeof(int); 23 | 24 | /// 25 | public override T ReadFromHandle(IntPtr ptr, int memSize) => ReadFromInt32(Marshal.ReadInt32(ptr)); 26 | 27 | /// 28 | public override void WriteToHandle(T obj, IntPtr ptr) => Marshal.WriteInt32(ptr, WriteToInt32(obj)); 29 | } 30 | 31 | /// 32 | /// Reads an Int32 from the clipboard. 33 | /// 34 | public class Int32DataConverter : Int32DataConverterBase 35 | { 36 | /// 37 | public override int ReadFromInt32(int val) => val; 38 | 39 | /// 40 | public override int WriteToInt32(int obj) => obj; 41 | } 42 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Formats/LocaleConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace Clowd.Clipboard.Formats; 4 | 5 | /// 6 | /// Used by CF_LOCALE, which is stored as an integer (lcid) and is represented by . 7 | /// 8 | public class LocaleConverter : Int32DataConverterBase 9 | { 10 | /// 11 | public override CultureInfo ReadFromInt32(int val) => new CultureInfo(val); 12 | 13 | /// 14 | public override int WriteToInt32(CultureInfo obj) => obj.LCID; 15 | } 16 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Formats/TextEncodingConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Clowd.Clipboard.Formats; 4 | 5 | /// 6 | /// A base class for encoding strings for the clipboard. 7 | /// 8 | public abstract class TextEncodingConverterBase : BytesDataConverterBase 9 | { 10 | /// 11 | /// Gets the encoder used to convert a string to bytes and back. 12 | /// 13 | public abstract Encoding GetEncoding(); 14 | 15 | /// 16 | /// Read a string from the specified bytes 17 | /// 18 | public override string ReadFromBytes(byte[] data) 19 | => GetEncoding().GetString(data).TrimEnd('\0'); 20 | 21 | /// 22 | /// Converts the specified string to bytes 23 | /// 24 | public override byte[] WriteToBytes(string obj) 25 | // strings need to have 1-2 null terminating characters (depends on encoding) but extra are harmless 26 | => GetEncoding().GetBytes(String.Concat(obj, "\0\0")); 27 | } 28 | 29 | /// 30 | /// For ANSI/MultiByte encoded strings. 31 | /// 32 | public class TextAnsiConverter : TextEncodingConverterBase 33 | { 34 | static TextAnsiConverter() 35 | { 36 | // .net core doesn't have ansi codepages available by default 37 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 38 | } 39 | 40 | /// 41 | public override Encoding GetEncoding() => Encoding.GetEncoding(Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage); 42 | } 43 | 44 | /// 45 | /// For widechar encoded strings. 46 | /// 47 | public class TextUnicodeConverter : TextEncodingConverterBase 48 | { 49 | /// 50 | public override Encoding GetEncoding() => Encoding.Unicode; 51 | } 52 | 53 | /// 54 | /// For UTF-8 encoded strings. 55 | /// 56 | public class TextUtf8Converter : TextEncodingConverterBase 57 | { 58 | /// 59 | public override Encoding GetEncoding() => Encoding.UTF8; 60 | } 61 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/Globals.cs: -------------------------------------------------------------------------------- 1 | global using global::System; 2 | global using global::System.Collections.Generic; 3 | global using global::System.IO; 4 | global using global::System.Linq; 5 | global using global::System.Threading; 6 | global using global::System.Threading.Tasks; 7 | global using global::System.Runtime.Versioning; 8 | 9 | #if !NET6_0_OR_GREATER 10 | namespace System.Runtime.Versioning 11 | { 12 | [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] 13 | internal class SupportedOSPlatformGuardAttribute : Attribute 14 | { 15 | public SupportedOSPlatformGuardAttribute(string platformName) { } 16 | } 17 | } 18 | #endif 19 | 20 | #if NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER 21 | namespace System.Runtime.Versioning 22 | { 23 | [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] 24 | internal class SupportedOSPlatformAttribute : Attribute 25 | { 26 | public SupportedOSPlatformAttribute(string platformName) { } 27 | } 28 | } 29 | 30 | namespace System.Runtime.CompilerServices 31 | { 32 | internal static class IsExternalInit { } 33 | } 34 | #endif -------------------------------------------------------------------------------- /src/Clowd.Clipboard/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text; 3 | 4 | namespace Clowd.Clipboard; 5 | 6 | [SupportedOSPlatform("windows")] 7 | internal class NativeMethods 8 | { 9 | public const int S_OK = 0x00000000; 10 | public const int S_FALSE = 0x00000001; 11 | 12 | public const int 13 | E_NOTIMPL = unchecked((int)0x80004001), 14 | E_OUTOFMEMORY = unchecked((int)0x8007000E), 15 | E_INVALIDARG = unchecked((int)0x80070057), 16 | E_NOINTERFACE = unchecked((int)0x80004002), 17 | E_FAIL = unchecked((int)0x80004005), 18 | E_ABORT = unchecked((int)0x80004004), 19 | E_ACCESSDENIED = unchecked((int)0x80070005), 20 | E_UNEXPECTED = unchecked((int)0x8000FFFF), 21 | CO_E_UNINITIALIZED = -2147221008, 22 | CLIPBRD_E_CANT_OPEN = -2147221040; 23 | 24 | public const int GMEM_MOVEABLE = 0x0002; 25 | public const int GMEM_ZEROINIT = 0x0040; 26 | 27 | // SHELL32 28 | 29 | [DllImport("shell32.dll", CharSet = CharSet.Auto)] 30 | public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch); 31 | 32 | // USER32 33 | 34 | [DllImport("user32.dll")] 35 | public static extern IntPtr GetOpenClipboardWindow(); 36 | 37 | [DllImport("user32.dll", SetLastError = true)] 38 | public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 39 | 40 | [DllImport("user32.dll", CharSet = CharSet.Auto, BestFitMapping = false, SetLastError = true)] 41 | public static extern int GetClipboardFormatName(uint format, StringBuilder lpString, int cchMax); 42 | 43 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto, BestFitMapping = false)] 44 | public static extern uint RegisterClipboardFormat(string format); 45 | 46 | [DllImport("user32.dll", EntryPoint = "CreateWindowExW", SetLastError = true)] 47 | public static extern IntPtr CreateWindowEx(int dwExStyle, [MarshalAs(UnmanagedType.LPWStr)] string lpClassName, 48 | [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, int dwStyle, int x, int y, 49 | int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, 50 | IntPtr lpParam); 51 | 52 | [DllImport("user32.dll", SetLastError = true)] 53 | public static extern bool DestroyWindow(IntPtr hWnd); 54 | 55 | [DllImport("user32.dll", SetLastError = true)] 56 | public static extern bool UnregisterClass(string lpClassName, IntPtr hInstance); 57 | 58 | [DllImport("user32.dll")] 59 | public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wparam, IntPtr lparam); 60 | 61 | [DllImport("user32.dll", EntryPoint = "RegisterClassW", SetLastError = true)] 62 | public static extern short RegisterClass(ref WindowClass lpWndClass); 63 | 64 | [DllImport("user32.dll", EntryPoint = "RegisterWindowMessageW")] 65 | public static extern uint RegisterWindowMessage([MarshalAs(UnmanagedType.LPWStr)] string lpString); 66 | 67 | [DllImport("user32.dll", SetLastError = true)] 68 | public static extern bool OpenClipboard(IntPtr hWndNewOwner); 69 | 70 | [DllImport("user32.dll", SetLastError = true)] 71 | public static extern bool CloseClipboard(); 72 | 73 | [DllImport("user32.dll", SetLastError = true)] 74 | public static extern bool EmptyClipboard(); 75 | 76 | [DllImport("user32.dll", SetLastError = true)] 77 | public static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem); 78 | 79 | [DllImport("user32.dll", SetLastError = true)] 80 | public static extern IntPtr GetClipboardData(uint uFormat); 81 | 82 | [DllImport("user32.dll", SetLastError = true)] 83 | public static extern uint EnumClipboardFormats(uint format); 84 | 85 | [DllImport("user32.dll", SetLastError = true)] 86 | public static extern int CountClipboardFormats(); 87 | 88 | // KERNEL32 89 | 90 | [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] 91 | public static extern IntPtr GlobalAlloc(int uFlags, int dwBytes); 92 | 93 | [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] 94 | public static extern IntPtr GlobalLock(IntPtr handle); 95 | 96 | [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)] 97 | public static extern bool GlobalUnlock(IntPtr handle); 98 | 99 | [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)] 100 | public static extern int GlobalSize(IntPtr handle); 101 | 102 | [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)] 103 | public static extern IntPtr GlobalFree(IntPtr handle); 104 | 105 | [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)] 106 | public static extern IntPtr GlobalReAlloc(IntPtr handle, int bytes, int flags); 107 | 108 | [DllImport("kernel32.dll", ExactSpelling = true, EntryPoint = "RtlMoveMemory", CharSet = CharSet.Unicode)] 109 | public static extern void CopyMemoryW(IntPtr pdst, char[] psrc, int cb); 110 | 111 | [DllImport("kernel32.dll", ExactSpelling = true, EntryPoint = "RtlMoveMemory")] 112 | public static extern void CopyMemory(IntPtr pdst, byte[] psrc, int cb); 113 | 114 | [DllImport("kernel32.dll", ExactSpelling = true, EntryPoint = "RtlMoveMemory", CharSet = CharSet.Ansi)] 115 | public static extern void CopyMemoryA(IntPtr pdst, char[] psrc, int cb); 116 | 117 | [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)] 118 | public static extern int WideCharToMultiByte(int codePage, int flags, [MarshalAs(UnmanagedType.LPWStr)]string wideStr, int chars, [In, Out]byte[] pOutBytes, int bufferBytes, IntPtr defaultChar, IntPtr pDefaultUsed); 119 | 120 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 121 | public static extern int lstrlen(String s); 122 | 123 | // GDI32 124 | 125 | [DllImport("gdi32.dll")] 126 | public static extern bool DeleteObject(IntPtr hObject); 127 | } 128 | -------------------------------------------------------------------------------- /src/Clowd.Clipboard/NativeStructs.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Clowd.Clipboard; 4 | 5 | internal delegate IntPtr WindowProcedureHandler(IntPtr hwnd, uint uMsg, IntPtr wparam, IntPtr lparam); 6 | 7 | [StructLayout(LayoutKind.Sequential)] 8 | internal struct WindowClass 9 | { 10 | public uint style; 11 | public WindowProcedureHandler lpfnWndProc; 12 | public int cbClsExtra; 13 | public int cbWndExtra; 14 | public IntPtr hInstance; 15 | public IntPtr hIcon; 16 | public IntPtr hCursor; 17 | public IntPtr hbrBackground; 18 | [MarshalAs(UnmanagedType.LPWStr)] public string lpszMenuName; 19 | [MarshalAs(UnmanagedType.LPWStr)] public string lpszClassName; 20 | } 21 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | $(MSBuildProjectName) 7 | $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\)) 8 | $(BaseOutputPath)obj\$(ProjectName)\ 9 | $(BaseOutputPath)$(Configuration)\ 10 | true 11 | false 12 | 13 | true 14 | 10 15 | caesay 16 | Caelan Sayler 17 | https://github.com/clowd/Clowd.Clipboard 18 | https://github.com/clowd/Clowd.Clipboard 19 | MIT 20 | Clowd_200.png 21 | A lightweight windows clipboard and bitmap parsing library. 22 | windows;clipboard;bitmaps;dib;parser;interop;gdi;wpf;avalonia;avaloniaui 23 | True 24 | ..\..\Clowd.Clipboard.snk 25 | embedded 26 | true 27 | $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))=./ 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/ClipboardAvaloniaTest/App.axaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/ClipboardAvaloniaTest/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | 5 | namespace ClipboardAvaloniaTest 6 | { 7 | public partial class App : Application 8 | { 9 | public override void Initialize() 10 | { 11 | AvaloniaXamlLoader.Load(this); 12 | } 13 | 14 | public override void OnFrameworkInitializationCompleted() 15 | { 16 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 17 | { 18 | desktop.MainWindow = new MainWindow(); 19 | } 20 | 21 | base.OnFrameworkInitializationCompleted(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/ClipboardAvaloniaTest/ClipboardAvaloniaTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net8.0 5 | enable 6 | copyused 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/ClipboardAvaloniaTest/MainWindow.axaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/ClipboardAvaloniaTest/MainWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CA1416 // Validate platform compatibility 2 | 3 | using Avalonia.Controls; 4 | using Avalonia.Interactivity; 5 | using Avalonia.Media.Imaging; 6 | using Clowd.Clipboard; 7 | using System; 8 | 9 | namespace ClipboardAvaloniaTest 10 | { 11 | public partial class MainWindow : Window 12 | { 13 | public MainWindow() 14 | { 15 | InitializeComponent(); 16 | btn.Click += Clicked; 17 | } 18 | 19 | private void Clicked(object? sender, RoutedEventArgs e) 20 | { 21 | var bmp = ClipboardAvalonia.GetImage(); 22 | img.Source = bmp; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /tests/ClipboardAvaloniaTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.Controls; 4 | using Avalonia.Controls.ApplicationLifetimes; 5 | 6 | namespace ClipboardAvaloniaTest 7 | { 8 | class Program 9 | { 10 | // Initialization code. Don't use any Avalonia, third-party APIs or any 11 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 12 | // yet and stuff might break. 13 | [STAThread] 14 | public static void Main(string[] args) => BuildAvaloniaApp() 15 | .StartWithClassicDesktopLifetime(args); 16 | 17 | // Avalonia configuration, don't remove; also used by visual designer. 18 | public static AppBuilder BuildAvaloniaApp() 19 | => AppBuilder.Configure() 20 | .UsePlatformDetect() 21 | .LogToTrace(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/ClipboardConsoleTests/ClipboardConsoleTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows 4 | Exe 5 | true 6 | true 7 | true 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/ClipboardConsoleTests/Program.cs: -------------------------------------------------------------------------------- 1 | using Clowd.Clipboard; 2 | using Clowd.Clipboard.Formats; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Windows.Media.Imaging; 12 | 13 | namespace ConsoleTests 14 | { 15 | class Program 16 | { 17 | unsafe static void Main(string[] args) 18 | { 19 | //0-64 20 | //0-256 21 | //0b_1111_1000; 22 | 23 | //uint maskR = 0xF800; 24 | //uint bR = 0b1111_1000_0000_0000; 25 | 26 | //if (val & 0xFFFFFF00 == 0) 27 | //{ 28 | 29 | //} 30 | 31 | //if(val <= 0xFF) 32 | 33 | //maskG = 0x03e0; 34 | //maskB = 0x001f; 35 | while (true) 36 | { 37 | 38 | using (var handle = new ClipboardHandleWpf()) 39 | { 40 | handle.Open(); 41 | var formats = handle.GetPresentFormats().ToArray(); 42 | 43 | Console.WriteLine("Formats: "); 44 | foreach (var f in formats) 45 | { 46 | Console.WriteLine(" - " + f.Name); 47 | 48 | try 49 | { 50 | var test = handle.GetFormatType(f, new TextUtf8Converter()); 51 | 52 | if (test != null && test.Length > 200) 53 | test = test.Substring(0, 200); 54 | 55 | Console.WriteLine(" > " + test); 56 | } 57 | catch { } 58 | } 59 | 60 | 61 | //byte[] bytes; 62 | //Stopwatch sw = new Stopwatch(); 63 | //sw.Start(); 64 | //var v3s = sw.ElapsedMilliseconds; 65 | //bytes = handle.GetFormat((ClipboardFormat)ClipboardFormat.Dib); 66 | //var v3e = sw.ElapsedMilliseconds; 67 | //var v5s = sw.ElapsedMilliseconds; 68 | //bytes = handle.GetFormat((ClipboardFormat)ClipboardFormat.DibV5); 69 | //var v5e = sw.ElapsedMilliseconds; 70 | 71 | //var v3 = v3e - v5s; 72 | //var v5 = v5e - v5e; 73 | //Console.WriteLine(); 74 | //bytes = handle.GetFormat((ClipboardFormat)ClipboardFormat.Dib); 75 | //File.WriteAllBytes("ROMAN-2.bmp", bytes); 76 | 77 | //handle.SetFormat((ClipboardFormat)ClipboardFormat.Dib, bytes); 78 | 79 | 80 | //var formats = handle.GetPresentFormats().ToArray(); 81 | //var count = handle.count(); 82 | //string app = "paintdotnet"; 83 | //var desired = formats.First(f => f == ClipboardFormat.Dib || f == ClipboardFormat.DibV5); 84 | 85 | //bytes = handle.GetFormat((ClipboardFormat)desired); 86 | //File.WriteAllBytes($"clip-{app}-desired.bmp", bytes); 87 | 88 | //bytes = handle.GetFormat((ClipboardFormat)ClipboardFormat.DibV5); 89 | //File.WriteAllBytes($"clip-{app}-dibv5.bmp", bytes); 90 | 91 | //bytes = handle.GetFormat((ClipboardFormat)ClipboardFormat.Dib); 92 | //File.WriteAllBytes($"clip-{app}-dib.bmp", bytes); 93 | } 94 | Console.ReadLine(); 95 | 96 | } 97 | 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/ClipboardConsoleTests/bitmapreadtest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardConsoleTests/bitmapreadtest.png -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/BitmapTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Reflection; 8 | using System.IO; 9 | using System.Windows.Media.Imaging; 10 | 11 | namespace ClipboardGapWpf.Tests 12 | { 13 | class BmpResource 14 | { 15 | public string Name; 16 | public byte[] Bytes; 17 | } 18 | 19 | [TestClass] 20 | public class BitmapTests 21 | { 22 | public IEnumerable ImageResourceNames => Assembly.GetExecutingAssembly().GetManifestResourceNames().Where(n => n.EndsWith(".bmp")); 23 | 24 | IEnumerable TestImages() 25 | { 26 | foreach (var name in ImageResourceNames) 27 | { 28 | yield return new BmpResource() 29 | { 30 | Name = Path.GetFileName(name), 31 | Bytes = ReadAllBytesAndDispose(Assembly.GetExecutingAssembly().GetManifestResourceStream(name)) 32 | }; 33 | } 34 | } 35 | 36 | public static byte[] ReadAllBytesAndDispose(Stream stream) 37 | { 38 | using (stream) 39 | { 40 | byte[] buffer = new byte[32768]; 41 | using (MemoryStream ms = new MemoryStream()) 42 | { 43 | while (true) 44 | { 45 | int read = stream.Read(buffer, 0, buffer.Length); 46 | if (read <= 0) 47 | return ms.ToArray(); 48 | ms.Write(buffer, 0, read); 49 | } 50 | } 51 | } 52 | } 53 | 54 | //[TestMethod] 55 | //public void WPFCheckAll() 56 | //{ 57 | // var tests = TestImages().ToArray(); 58 | 59 | // MemoryStream ms = new MemoryStream(); 60 | // for (int ir = 0; ir < tests.Length; ir++) 61 | // { 62 | // BmpResource resource = (BmpResource)tests[ir]; 63 | // var bytes = resource.Bytes; 64 | 65 | // var decoder = new BmpBitmapDecoder(new MemoryStream(bytes), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); 66 | // var bitmap = decoder.Frames[0]; 67 | 68 | // BmpBitmapEncoder encoder = new BmpBitmapEncoder(); 69 | // encoder.Frames.Add(BitmapFrame.Create(bitmap)); 70 | 71 | // ms.SetLength(0); 72 | // encoder.Save(ms); 73 | 74 | // var bytes2 = ms.GetBuffer(); 75 | 76 | // var head1 = StructUtil.Deserialize(bytes, 0); 77 | // var head2 = StructUtil.Deserialize(bytes2, 0); 78 | 79 | // var info1 = StructUtil.Deserialize(bytes, 14); 80 | // var info2 = StructUtil.Deserialize(bytes2, 14); 81 | 82 | // Assert.AreEqual(head1.bfType, head2.bfType); 83 | // Assert.AreEqual(head1.bfOffBits, head2.bfOffBits); 84 | // Assert.AreEqual(head1.bfSize, head2.bfSize); 85 | 86 | // Assert.AreEqual(info1.bV5Size, info2.bV5Size); 87 | // Assert.AreEqual(info1.bV5Width, info2.bV5Width); 88 | // Assert.AreEqual(info1.bV5Height, info2.bV5Height); 89 | // Assert.AreEqual(info1.bV5Planes, info2.bV5Planes); 90 | // Assert.AreEqual(info1.bV5BitCount, info2.bV5BitCount); 91 | // Assert.AreEqual(info1.bV5Compression, info2.bV5Compression); 92 | // //Assert.AreEqual(info1.bV5SizeImage, info2.bV5SizeImage); 93 | // Assert.AreEqual(info1.bV5XPelsPerMeter, info2.bV5XPelsPerMeter); 94 | // Assert.AreEqual(info1.bV5YPelsPerMeter, info2.bV5YPelsPerMeter); 95 | // Assert.AreEqual(info1.bV5ClrUsed, info2.bV5ClrUsed); 96 | // //Assert.AreEqual(info1.bV5ClrImportant, info2.bV5ClrImportant); 97 | 98 | // Console.WriteLine(); 99 | 100 | // for (uint i = head1.bfOffBits; i < bytes.Length; i++) 101 | // { 102 | // var b1 = bytes[i]; 103 | // var b2 = bytes2[i]; 104 | 105 | // if (b1 != b2) 106 | // Assert.Fail($"Position {i}, file '{resource.Name}': Expected {(int)b1}, Actual {(int)b2}"); 107 | // } 108 | 109 | // } 110 | //} 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/ClipboardUnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Debug 4 | ClipboardGapWpf.Tests 5 | ClipboardGapWpf.Tests 6 | net8.0-windows 7 | true 8 | true 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/StringTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | using Clowd.Clipboard; 9 | 10 | namespace ClipboardGapWpf.Tests 11 | { 12 | [TestClass] 13 | public class StringTests 14 | { 15 | private string _text; 16 | public StringTests() 17 | { 18 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ClipboardGapWpf.Tests.utf8.txt")) 19 | using (var reader = new StreamReader(stream, Encoding.UTF8, false)) 20 | _text = reader.ReadToEnd(); 21 | } 22 | 23 | private string EncodeNonAsciiCharacters(string value) 24 | { 25 | StringBuilder sb = new StringBuilder(); 26 | foreach (char c in value) 27 | { 28 | if (c > 127) 29 | { 30 | // This character is too big for ASCII 31 | string encodedValue = "\\u" + ((int)c).ToString("x4"); 32 | sb.Append(encodedValue); 33 | } 34 | else 35 | { 36 | sb.Append(c); 37 | } 38 | } 39 | return sb.ToString(); 40 | } 41 | 42 | private void StrCompare(string stcp, string reference = null) 43 | { 44 | if (reference == null) 45 | reference = _text; 46 | 47 | for (int i = 0; i < reference.Length; i++) 48 | { 49 | var c1 = reference[i]; 50 | var c2 = stcp[i]; 51 | 52 | if (c1 != c2) 53 | Assert.Fail($"Char position {i}/{_text.Length}: Expected '{c1}' ({(int)c1} {EncodeNonAsciiCharacters(c1.ToString())}), Actual '{c2}' ({(int)c2} {EncodeNonAsciiCharacters(c2.ToString())})."); 54 | } 55 | } 56 | 57 | [TestMethod] 58 | public void U16toU16_Inline() 59 | { 60 | string round; 61 | using (var handle = new ClipboardHandleWpf()) 62 | { 63 | handle.Open(); 64 | handle.SetFormat(ClipboardFormat.UnicodeText, _text); 65 | round = handle.GetFormatType(ClipboardFormat.UnicodeText); 66 | handle.Empty(); 67 | } 68 | StrCompare(round); 69 | } 70 | 71 | [TestMethod] 72 | public void U16toU16_Break() 73 | { 74 | string round; 75 | using (var handle = new ClipboardHandleWpf()) 76 | { 77 | handle.Open(); 78 | handle.SetFormat(ClipboardFormat.UnicodeText, _text); 79 | } 80 | using (var handle = new ClipboardHandleWpf()) 81 | { 82 | handle.Open(); 83 | round = handle.GetFormatType(ClipboardFormat.UnicodeText); 84 | handle.Empty(); 85 | } 86 | StrCompare(round); 87 | } 88 | 89 | [TestMethod] 90 | public void ANSItoU16() 91 | { 92 | var reference = "Hello, I am a test! :)"; 93 | string round; 94 | using (var handle = new ClipboardHandleWpf()) 95 | { 96 | handle.Open(); 97 | handle.SetFormat(ClipboardFormat.Text, reference); 98 | } 99 | using (var handle = new ClipboardHandleWpf()) 100 | { 101 | handle.Open(); 102 | round = handle.GetFormatType(ClipboardFormat.UnicodeText); 103 | handle.Empty(); 104 | } 105 | StrCompare(round, reference); 106 | } 107 | 108 | [TestMethod] 109 | public void U16toANSI() 110 | { 111 | var reference = "Hello, I am a test! :)"; 112 | string round; 113 | using (var handle = new ClipboardHandleWpf()) 114 | { 115 | handle.Open(); 116 | handle.SetFormat(ClipboardFormat.UnicodeText, reference); 117 | } 118 | using (var handle = new ClipboardHandleWpf()) 119 | { 120 | handle.Open(); 121 | round = handle.GetFormatType(ClipboardFormat.Text); 122 | handle.Empty(); 123 | } 124 | StrCompare(round, reference); 125 | } 126 | 127 | [TestMethod] 128 | public void ANSI_Encode() 129 | { 130 | var converter = new Clowd.Clipboard.Formats.TextAnsiConverter(); 131 | var hglobal = converter.WriteToHGlobal(_text); 132 | var back = converter.ReadFromHGlobal(hglobal); 133 | var ansi = Encoding.GetEncoding(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage); 134 | var tnansi = ansi.GetString(ansi.GetBytes(_text)); 135 | StrCompare(back, tnansi); 136 | } 137 | 138 | [TestMethod] 139 | public void UTF16_Encode() 140 | { 141 | var converter = new Clowd.Clipboard.Formats.TextUnicodeConverter(); 142 | var hglobal = converter.WriteToHGlobal(_text); 143 | var back = converter.ReadFromHGlobal(hglobal); 144 | var enc = Encoding.Unicode; 145 | var fx = enc.GetString(enc.GetBytes(_text)); 146 | StrCompare(back, fx); 147 | } 148 | 149 | [TestMethod] 150 | public void UTF8_Encode() 151 | { 152 | var converter = new Clowd.Clipboard.Formats.TextUtf8Converter(); 153 | var hglobal = converter.WriteToHGlobal(_text); 154 | var back = converter.ReadFromHGlobal(hglobal); 155 | var enc = Encoding.UTF8; 156 | var fx = enc.GetString(enc.GetBytes(_text)); 157 | StrCompare(back, fx); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bitmapreadtest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bitmapreadtest.png -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/pal1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/pal1.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/pal1bg.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/pal1bg.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/pal4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/pal4.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/pal8.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/pal8.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/pal8v5.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/pal8v5.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/pal8w124.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/pal8w124.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/pal8w125.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/pal8w125.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/pal8w126.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/pal8w126.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgb16-565.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgb16-565.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgb16-565pal.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgb16-565pal.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgb16.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgb16.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgb16bfdef.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgb16bfdef.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgb24.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgb24.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgb24pal.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgb24pal.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgb32-7187.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgb32-7187.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgb32.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgb32.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgb32bf.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgb32bf.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgb32bfdef.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgb32bfdef.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgba32-1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgba32-1.bmp -------------------------------------------------------------------------------- /tests/ClipboardUnitTests/bmp/rgba32-2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clowd/Clowd.Clipboard/2ccf29267d5dcbba9ce219542e84709033edb5b3/tests/ClipboardUnitTests/bmp/rgba32-2.bmp -------------------------------------------------------------------------------- /tests/ClipboardWpfTests/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/ClipboardWpfTests/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 WpfTests 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/ClipboardWpfTests/ClipboardWpfTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows 4 | WinExe 5 | true 6 | app.manifest 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/ClipboardWpfTests/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |