├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── build-and-test.yml │ └── codeql-analysis.yml ├── .gitignore ├── .gitmodules ├── Directory.Build.props ├── Directory.Build.targets ├── LICENSE ├── NuGet.config ├── THIRD-PARTY-NOTICES.txt ├── ZlibStream.sln ├── benchmarks.md ├── ci-build.ps1 ├── ci-pack.ps1 ├── ci-test.ps1 ├── readme.md ├── src ├── Directory.Build.props ├── Directory.Build.targets └── ZlibStream │ ├── Adler32.cs │ ├── ArrayExtensions.cs │ ├── CompressionLevel.cs │ ├── CompressionState.cs │ ├── CompressionStrategy.cs │ ├── Deflate.Buffers.cs │ ├── Deflate.Fast.cs │ ├── Deflate.Intrinsics.cs │ ├── Deflate.Quick.cs │ ├── Deflate.Rle.cs │ ├── Deflate.Slow.cs │ ├── Deflate.Stored.cs │ ├── Deflate.cs │ ├── FlushMode.cs │ ├── InfCodes.cs │ ├── InfTree.cs │ ├── Inflate.cs │ ├── InflateBlocks.cs │ ├── InliningOptions.cs │ ├── SerializableAttribute.cs │ ├── StreamExtensions.cs │ ├── ThrowHelper.cs │ ├── Trees.Dynamic.cs │ ├── Trees.Static.cs │ ├── Trees.cs │ ├── ZlibInputStream.cs │ ├── ZlibOptions.cs │ ├── ZlibOutputStream.cs │ ├── ZlibStream.cs │ ├── ZlibStream.csproj │ └── ZlibStreamException.cs ├── stylecop.json └── tests ├── Directory.Build.props ├── Directory.Build.targets ├── ZlibStream.Benchmarks ├── Adler32Benchmark.cs ├── BinaryPrimitiveBenchmarks.cs ├── Config.cs ├── DeflateCorpusBenchmark.cs ├── DeflateSparseBenchmark.cs ├── DotNetZlibDeflateStream.cs ├── Program.cs └── ZlibStream.Benchmarks.csproj ├── ZlibStream.Tests ├── Adler32Tests.cs ├── TestUtilities │ ├── Corpus.cs │ └── TestEnvironment.cs ├── ZlibStream.Tests.csproj ├── ZlibStreamTests.Roundtrip.cs └── xunit.runner.json ├── corpus ├── alice29.txt ├── asyoulik.txt ├── cp.html ├── fields.c ├── grammar.lsp ├── kennedy.xls ├── lcet10.txt ├── plrabn12.txt ├── ptt5 ├── sum └── xargs.1 └── coverlet.runsettings /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to: 3 | # treat as text and 4 | # normalize to Unix-style line endings 5 | ############################################################################### 6 | * text eol=lf 7 | ############################################################################### 8 | # Set explicit file behavior to: 9 | # treat as text and 10 | # normalize to Unix-style line endings 11 | ############################################################################### 12 | *.asm text eol=lf 13 | *.c text eol=lf 14 | *.clj text eol=lf 15 | *.cmd text eol=lf 16 | *.cpp text eol=lf 17 | *.css text eol=lf 18 | *.cxx text eol=lf 19 | *.config text eol=lf 20 | *.DotSettings text eol=lf 21 | *.erl text eol=lf 22 | *.fs text eol=lf 23 | *.fsx text eol=lf 24 | *.h text eol=lf 25 | *.htm text eol=lf 26 | *.html text eol=lf 27 | *.hs text eol=lf 28 | *.hxx text eol=lf 29 | *.java text eol=lf 30 | *.js text eol=lf 31 | *.json text eol=lf 32 | *.less text eol=lf 33 | *.lisp text eol=lf 34 | *.lua text eol=lf 35 | *.m text eol=lf 36 | *.md text eol=lf 37 | *.php text eol=lf 38 | *.props text eol=lf 39 | *.ps1 text eol=lf 40 | *.py text eol=lf 41 | *.rb text eol=lf 42 | *.resx text eol=lf 43 | *.runsettings text eol=lf 44 | *.ruleset text eol=lf 45 | *.sass text eol=lf 46 | *.scss text eol=lf 47 | *.sh text eol=lf 48 | *.sql text eol=lf 49 | *.svg text eol=lf 50 | *.targets text eol=lf 51 | *.tt text eol=crlf 52 | *.ttinclude text eol=crlf 53 | *.txt text eol=lf 54 | *.vb text eol=lf 55 | *.yml text eol=lf 56 | ############################################################################### 57 | # Set explicit file behavior to: 58 | # treat as text 59 | # normalize to Unix-style line endings and 60 | # diff as csharp 61 | ############################################################################### 62 | *.cs text eol=lf diff=csharp 63 | ############################################################################### 64 | # Set explicit file behavior to: 65 | # treat as text 66 | # normalize to Unix-style line endings and 67 | # use a union merge when resoling conflicts 68 | ############################################################################### 69 | *.csproj text eol=lf merge=union 70 | *.dbproj text eol=lf merge=union 71 | *.fsproj text eol=lf merge=union 72 | *.ncrunchproject text eol=lf merge=union 73 | *.vbproj text eol=lf merge=union 74 | ############################################################################### 75 | # Set explicit file behavior to: 76 | # treat as text 77 | # normalize to Windows-style line endings and 78 | # use a union merge when resoling conflicts 79 | ############################################################################### 80 | *.sln text eol=crlf merge=union 81 | ############################################################################### 82 | # Set explicit file behavior to: 83 | # treat as binary 84 | ############################################################################### 85 | *.basis binary 86 | *.dll binary 87 | *.eot binary 88 | *.exe binary 89 | *.otf binary 90 | *.pbm binary 91 | *.pdf binary 92 | *.ppt binary 93 | *.pptx binary 94 | *.pvr binary 95 | *.snk binary 96 | *.ttc binary 97 | *.ttf binary 98 | *.wbmp binary 99 | *.woff binary 100 | *.woff2 binary 101 | *.xls binary 102 | *.xlsx binary 103 | ############################################################################### 104 | # Set explicit file behavior to: 105 | # diff as plain text 106 | ############################################################################### 107 | *.doc diff=astextplain 108 | *.docx diff=astextplain 109 | *.dot diff=astextplain 110 | *.pdf diff=astextplain 111 | *.pptx diff=astextplain 112 | *.rtf diff=astextplain 113 | *.svg diff=astextplain 114 | ############################################################################### 115 | # Handle image files by git lfs 116 | ############################################################################### 117 | *.jpg filter=lfs diff=lfs merge=lfs -text 118 | *.jpeg filter=lfs diff=lfs merge=lfs -text 119 | *.bmp filter=lfs diff=lfs merge=lfs -text 120 | *.gif filter=lfs diff=lfs merge=lfs -text 121 | *.png filter=lfs diff=lfs merge=lfs -text 122 | *.tif filter=lfs diff=lfs merge=lfs -text 123 | *.tiff filter=lfs diff=lfs merge=lfs -text 124 | *.tga filter=lfs diff=lfs merge=lfs -text 125 | *.webp filter=lfs diff=lfs merge=lfs -text 126 | *.dds filter=lfs diff=lfs merge=lfs -text 127 | *.ktx filter=lfs diff=lfs merge=lfs -text 128 | *.ktx2 filter=lfs diff=lfs merge=lfs -text 129 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "v*" 9 | pull_request: 10 | branches: 11 | - main 12 | jobs: 13 | Build: 14 | strategy: 15 | matrix: 16 | options: 17 | - os: ubuntu-latest 18 | framework: net5.0 19 | runtime: -x64 20 | codecov: false 21 | - os: macos-latest 22 | framework: net5.0 23 | runtime: -x64 24 | codecov: false 25 | - os: windows-latest 26 | framework: net5.0 27 | runtime: -x64 28 | codecov: false 29 | - os: ubuntu-latest 30 | framework: netcoreapp3.1 31 | runtime: -x64 32 | codecov: true 33 | - os: macos-latest 34 | framework: netcoreapp3.1 35 | runtime: -x64 36 | codecov: false 37 | - os: windows-latest 38 | framework: netcoreapp3.1 39 | runtime: -x64 40 | codecov: false 41 | - os: windows-latest 42 | framework: netcoreapp2.1 43 | runtime: -x64 44 | codecov: false 45 | - os: windows-latest 46 | framework: net472 47 | runtime: -x64 48 | codecov: false 49 | - os: windows-latest 50 | framework: net472 51 | runtime: -x86 52 | codecov: false 53 | 54 | runs-on: ${{matrix.options.os}} 55 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 56 | 57 | steps: 58 | - uses: actions/checkout@v3 59 | 60 | # See https://github.com/actions/checkout/issues/165#issuecomment-657673315 61 | - name: Create LFS file list 62 | run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id 63 | 64 | - name: Restore LFS cache 65 | uses: actions/cache@v3 66 | id: lfs-cache 67 | with: 68 | path: .git/lfs 69 | key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 70 | 71 | - name: Git LFS Pull 72 | run: git lfs pull 73 | 74 | - name: Install NuGet 75 | uses: NuGet/setup-nuget@v1 76 | 77 | - name: Setup Git 78 | shell: bash 79 | run: | 80 | git config --global core.autocrlf false 81 | git config --global core.longpaths true 82 | git fetch --prune --unshallow 83 | git submodule -q update --init --recursive 84 | 85 | - name: Setup NuGet Cache 86 | uses: actions/cache@v3 87 | id: nuget-cache 88 | with: 89 | path: ~/.nuget 90 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} 91 | restore-keys: ${{ runner.os }}-nuget- 92 | 93 | - name: DotNet Setup 94 | uses: actions/setup-dotnet@v2 95 | with: 96 | dotnet-version: | 97 | 5.0.x 98 | 3.1.x 99 | 2.1.x 100 | 101 | - name: Build 102 | shell: pwsh 103 | run: ./ci-build.ps1 "${{matrix.options.framework}}" 104 | env: 105 | SIXLABORS_TESTING: True 106 | 107 | - name: Test 108 | shell: pwsh 109 | run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" 110 | env: 111 | SIXLABORS_TESTING: True 112 | XUNIT_PATH: .\tests\ZLibStream.Tests # Required for xunit 113 | 114 | - name: Update Codecov 115 | uses: codecov/codecov-action@v3 116 | if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') 117 | with: 118 | flags: unittests 119 | 120 | Publish: 121 | needs: [Build] 122 | 123 | runs-on: windows-latest 124 | 125 | if: (github.event_name == 'push') 126 | 127 | steps: 128 | - uses: actions/checkout@v3 129 | 130 | - name: Install NuGet 131 | uses: NuGet/setup-nuget@v1 132 | 133 | - name: Setup Git 134 | shell: bash 135 | run: | 136 | git config --global core.autocrlf false 137 | git config --global core.longpaths true 138 | git fetch --prune --unshallow 139 | git submodule -q update --init --recursive 140 | 141 | - name: Pack 142 | shell: pwsh 143 | run: ./ci-pack.ps1 144 | 145 | - name: Publish to MyGet 146 | shell: pwsh 147 | run: | 148 | nuget.exe push .\artifacts\*.nupkg ${{secrets.MYGET_TOKEN}} -Source https://www.myget.org/F/sixlabors/api/v2/package 149 | nuget.exe push .\artifacts\*.snupkg ${{secrets.MYGET_TOKEN}} -Source https://www.myget.org/F/sixlabors/api/v3/index.json 150 | # TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org 151 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [main] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [main] 14 | schedule: 15 | - cron: '0 13 * * 4' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['csharp'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v3 34 | with: 35 | submodules: true 36 | 37 | # Initializes the CodeQL tools for scanning. 38 | - name: Initialize CodeQL 39 | uses: github/codeql-action/init@v2 40 | with: 41 | languages: ${{ matrix.language }} 42 | # If you wish to specify custom queries, you can do so here or in a config file. 43 | # By default, queries listed here will override any specified in a config file. 44 | # Prefix the list here with "+" to use these queries and those in the config file. 45 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 46 | 47 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 48 | # If this step fails, then you should remove it and run the build manually (see below) 49 | - name: Autobuild 50 | uses: github/codeql-action/autobuild@v2 51 | 52 | # ℹ️ Command-line programs to run using the OS shell. 53 | # 📚 https://git.io/JvXDl 54 | 55 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 56 | # and modify them (or add more) to build your code if your project 57 | # uses a compiled language 58 | 59 | #- run: | 60 | # make bootstrap 61 | # make release 62 | 63 | - name: Perform CodeQL Analysis 64 | uses: github/codeql-action/analyze@v2 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | src/**/build/ 21 | tests/**/build/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | 26 | # Visual Studo 2015 cache/options directory 27 | .vs/ 28 | 29 | # Jetbrains Rider cache/options directory 30 | .idea/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # ASP.NET 5 46 | project.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding addin-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | 154 | # Windows Azure Build Output 155 | csx/ 156 | *.build.csdef 157 | 158 | # Windows Store app package directory 159 | AppPackages/ 160 | 161 | # Others 162 | *.[Cc]ache 163 | ClientBin/ 164 | ~$* 165 | *~ 166 | *.dbmdl 167 | *.dbproj.schemaview 168 | *.pfx 169 | *.publishsettings 170 | node_modules/ 171 | bower_components/ 172 | 173 | # RIA/Silverlight projects 174 | Generated_Code/ 175 | 176 | # Backup & report files from converting an old project file 177 | # to a newer Visual Studio version. Backup files are not needed, 178 | # because we have git ;-) 179 | _UpgradeReport_Files/ 180 | Backup*/ 181 | UpgradeLog*.XML 182 | UpgradeLog*.htm 183 | 184 | # SQL Server files 185 | *.mdf 186 | *.ldf 187 | 188 | # Business Intelligence projects 189 | *.rdl.data 190 | *.bim.layout 191 | *.bim_*.settings 192 | 193 | # Microsoft Fakes 194 | FakesAssemblies/ 195 | 196 | # Node.js Tools for Visual Studio 197 | .ntvs_analysis.dat 198 | 199 | # Visual Studio 6 build log 200 | *.plg 201 | 202 | # Visual Studio 6 workspace options file 203 | *.opt 204 | 205 | **/node_modules 206 | **/node_modules/* 207 | 208 | # ASP.NET 5 209 | project.lock.json 210 | artifacts/ 211 | 212 | #BenchmarkDotNet 213 | **/BenchmarkDotNet.Artifacts/ 214 | 215 | # Build process 216 | *.csproj.bak 217 | 218 | #CodeCoverage 219 | *.lcov 220 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "shared-infrastructure"] 2 | path = shared-infrastructure 3 | url = https://github.com/SixLabors/SharedInfrastructure 4 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | $(MSBuildThisFileDirectory) 16 | 17 | 18 | 19 | 20 | 21 | 35 | 36 | 37 | 38 | 39 | $(DefineConstants);SUPPORTS_SERIALIZATION 40 | 41 | 42 | 43 | 44 | $(DefineConstants);SUPPORTS_SERIALIZATION 45 | 46 | 47 | 48 | 49 | $(DefineConstants);SUPPORTS_SERIALIZATION 50 | 51 | 52 | 53 | 54 | 55 | $(DefineConstants);SUPPORTS_SERIALIZATION 56 | $(DefineConstants);SUPPORTS_CORE_CLR 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) Six Labors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /THIRD-PARTY-NOTICES.txt: -------------------------------------------------------------------------------- 1 | ZlibStream is built upon code that was originally distributed under a license different than the ZlibStream software. 2 | 3 | In the event that we accidentally failed to list a required notice, please bring it to our attention. 4 | 5 | The attached notices are provided for information only. 6 | 7 | License notice for ComponentAce 8 | ------------------------------- 9 | 10 | Copyright (c) 2006, ComponentAce 11 | http://www.componentace.com 12 | All rights reserved. 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions are met: 16 | 17 | Redistributions of source code must retain the above copyright notice, 18 | this list of conditions and the following disclaimer. 19 | 20 | Redistributions in binary form must reproduce the above copyright 21 | notice, this list of conditions and the following disclaimer in 22 | the documentation and/or other materials provided with the distribution. 23 | 24 | Neither the name of ComponentAce nor the names of its contributors 25 | may be used to endorse or promote products derived from this 26 | software without specific prior written permission. 27 | 28 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 29 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 30 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 31 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 32 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 33 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 34 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 35 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 38 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 39 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | 41 | -------------------------------------------------------------------------------- 42 | 43 | Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. 44 | 45 | Redistribution and use in source and binary forms, with or without 46 | modification, are permitted provided that the following conditions are met: 47 | 48 | 1. Redistributions of source code must retain the above copyright notice, 49 | this list of conditions and the following disclaimer. 50 | 51 | 2. Redistributions in binary form must reproduce the above copyright 52 | notice, this list of conditions and the following disclaimer in 53 | the documentation and/or other materials provided with the distribution. 54 | 55 | 3. The names of the authors may not be used to endorse or promote products 56 | derived from this software without specific prior written permission. 57 | 58 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, 59 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 60 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, 61 | INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 62 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 63 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 64 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 65 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 66 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 67 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 68 | 69 | -------------------------------------------------------------------------------- 70 | 71 | This program is based on zlib-1.1.3, so all credit should go authors 72 | Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) 73 | and contributors of zlib. 74 | 75 | License notice for Zlib 76 | ------------------------------- 77 | 78 | Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler 79 | 80 | This software is provided 'as-is', without any express or implied 81 | warranty. In no event will the authors be held liable for any damages 82 | arising from the use of this software. 83 | 84 | Permission is granted to anyone to use this software for any purpose, 85 | including commercial applications, and to alter it and redistribute it 86 | freely, subject to the following restrictions: 87 | 88 | 1. The origin of this software must not be misrepresented; you must not 89 | claim that you wrote the original software. If you use this software 90 | in a product, an acknowledgment in the product documentation would be 91 | appreciated but is not required. 92 | 2. Altered source versions must be plainly marked as such, and must not be 93 | misrepresented as being the original software. 94 | 3. This notice may not be removed or altered from any source distribution. -------------------------------------------------------------------------------- /ZlibStream.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.30011.22 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZlibStream", "src\ZlibStream\ZlibStream.csproj", "{0C89B7A2-A218-49E4-B545-5B044A45F977}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{78670B24-8720-48FC-BD04-68C667B1C8CC}" 8 | ProjectSection(SolutionItems) = preProject 9 | src\Directory.Build.props = src\Directory.Build.props 10 | src\Directory.Build.targets = src\Directory.Build.targets 11 | EndProjectSection 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{CB611663-07F2-4419-B83D-7AED9B406865}" 14 | ProjectSection(SolutionItems) = preProject 15 | tests\Directory.Build.props = tests\Directory.Build.props 16 | tests\Directory.Build.targets = tests\Directory.Build.targets 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZlibStream.Tests", "tests\ZlibStream.Tests\ZlibStream.Tests.csproj", "{01E63166-CDBA-450F-A597-DE096B05A052}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZlibStream.Benchmarks", "tests\ZlibStream.Benchmarks\ZlibStream.Benchmarks.csproj", "{15FE1586-DED2-41F4-9292-6BE8787A60C4}" 22 | EndProject 23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{F450D162-1FE5-4295-BDB8-8F78F6B51672}" 24 | ProjectSection(SolutionItems) = preProject 25 | ci-build.ps1 = ci-build.ps1 26 | ci-pack.ps1 = ci-pack.ps1 27 | ci-test.ps1 = ci-test.ps1 28 | Directory.Build.props = Directory.Build.props 29 | Directory.Build.targets = Directory.Build.targets 30 | LICENSE = LICENSE 31 | readme.md = readme.md 32 | EndProjectSection 33 | EndProject 34 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Corpus", "Corpus", "{7211DB62-F3C8-419C-846B-89949EBD1EAC}" 35 | ProjectSection(SolutionItems) = preProject 36 | tests\corpus\alice29.txt = tests\corpus\alice29.txt 37 | tests\corpus\asyoulik.txt = tests\corpus\asyoulik.txt 38 | tests\corpus\cp.html = tests\corpus\cp.html 39 | tests\corpus\fields.c = tests\corpus\fields.c 40 | tests\corpus\grammar.lsp = tests\corpus\grammar.lsp 41 | tests\corpus\kennedy.xls = tests\corpus\kennedy.xls 42 | tests\corpus\lcet10.txt = tests\corpus\lcet10.txt 43 | tests\corpus\plrabn12.txt = tests\corpus\plrabn12.txt 44 | tests\corpus\ptt5 = tests\corpus\ptt5 45 | tests\corpus\sum = tests\corpus\sum 46 | tests\corpus\xargs.1 = tests\corpus\xargs.1 47 | EndProjectSection 48 | EndProject 49 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{42C81331-D4CA-432D-95ED-92FB1A121267}" 50 | EndProject 51 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{AA25F9A0-775E-4184-8086-02BAE1C1F941}" 52 | ProjectSection(SolutionItems) = preProject 53 | .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml 54 | .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml 55 | EndProjectSection 56 | EndProject 57 | Global 58 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 59 | Debug|Any CPU = Debug|Any CPU 60 | Release|Any CPU = Release|Any CPU 61 | EndGlobalSection 62 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 63 | {0C89B7A2-A218-49E4-B545-5B044A45F977}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {0C89B7A2-A218-49E4-B545-5B044A45F977}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {0C89B7A2-A218-49E4-B545-5B044A45F977}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {0C89B7A2-A218-49E4-B545-5B044A45F977}.Release|Any CPU.Build.0 = Release|Any CPU 67 | {01E63166-CDBA-450F-A597-DE096B05A052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {01E63166-CDBA-450F-A597-DE096B05A052}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {01E63166-CDBA-450F-A597-DE096B05A052}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {01E63166-CDBA-450F-A597-DE096B05A052}.Release|Any CPU.Build.0 = Release|Any CPU 71 | {15FE1586-DED2-41F4-9292-6BE8787A60C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 72 | {15FE1586-DED2-41F4-9292-6BE8787A60C4}.Debug|Any CPU.Build.0 = Debug|Any CPU 73 | {15FE1586-DED2-41F4-9292-6BE8787A60C4}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {15FE1586-DED2-41F4-9292-6BE8787A60C4}.Release|Any CPU.Build.0 = Release|Any CPU 75 | EndGlobalSection 76 | GlobalSection(SolutionProperties) = preSolution 77 | HideSolutionNode = FALSE 78 | EndGlobalSection 79 | GlobalSection(NestedProjects) = preSolution 80 | {0C89B7A2-A218-49E4-B545-5B044A45F977} = {78670B24-8720-48FC-BD04-68C667B1C8CC} 81 | {01E63166-CDBA-450F-A597-DE096B05A052} = {CB611663-07F2-4419-B83D-7AED9B406865} 82 | {15FE1586-DED2-41F4-9292-6BE8787A60C4} = {CB611663-07F2-4419-B83D-7AED9B406865} 83 | {7211DB62-F3C8-419C-846B-89949EBD1EAC} = {CB611663-07F2-4419-B83D-7AED9B406865} 84 | {42C81331-D4CA-432D-95ED-92FB1A121267} = {F450D162-1FE5-4295-BDB8-8F78F6B51672} 85 | {AA25F9A0-775E-4184-8086-02BAE1C1F941} = {42C81331-D4CA-432D-95ED-92FB1A121267} 86 | EndGlobalSection 87 | GlobalSection(ExtensibilityGlobals) = postSolution 88 | SolutionGuid = {AE022F12-D088-439B-8AD2-EAAF2FE7C6C0} 89 | EndGlobalSection 90 | EndGlobal 91 | -------------------------------------------------------------------------------- /ci-build.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Mandatory = $true, Position = 0)] 3 | [string]$targetFramework 4 | ) 5 | 6 | dotnet clean -c Release 7 | 8 | $repositoryUrl = "https://github.com/$env:GITHUB_REPOSITORY" 9 | 10 | # Building for a specific framework. 11 | dotnet build -c Release -f $targetFramework /p:RepositoryUrl=$repositoryUrl 12 | -------------------------------------------------------------------------------- /ci-pack.ps1: -------------------------------------------------------------------------------- 1 | dotnet clean -c Release 2 | 3 | $repositoryUrl = "https://github.com/$env:GITHUB_REPOSITORY" 4 | 5 | # Building for packing and publishing. 6 | dotnet pack -c Release --output "$PSScriptRoot/artifacts" /p:RepositoryUrl=$repositoryUrl 7 | -------------------------------------------------------------------------------- /ci-test.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Mandatory, Position = 0)] 3 | [string]$os, 4 | [Parameter(Mandatory, Position = 1)] 5 | [string]$targetFramework, 6 | [Parameter(Mandatory, Position = 2)] 7 | [string]$platform, 8 | [Parameter(Mandatory, Position = 3)] 9 | [string]$codecov, 10 | [Parameter(Position = 4)] 11 | [string]$codecovProfile = 'Release' 12 | ) 13 | 14 | $netFxRegex = '^net\d+' 15 | 16 | if ($codecov -eq 'true') { 17 | 18 | # Allow toggling of profile to workaround any potential JIT errors caused by code injection. 19 | dotnet clean -c $codecovProfile 20 | dotnet test --collect "XPlat Code Coverage" --settings .\tests\coverlet.runsettings -c $codecovProfile -f $targetFramework /p:CodeCov=true 21 | } 22 | elseif ($platform -eq '-x86' -and $targetFramework -match $netFxRegex) { 23 | 24 | # xunit doesn't run on core with NET SDK 3.1+. 25 | # xunit doesn't actually understand -x64 as an option. 26 | # 27 | # xunit requires explicit path. 28 | Set-Location $env:XUNIT_PATH 29 | 30 | dotnet xunit --no-build -c Release -f $targetFramework ${fxVersion} $platform 31 | 32 | Set-Location $PSScriptRoot 33 | } 34 | else { 35 | 36 | dotnet test --no-build -c Release -f $targetFramework 37 | } 38 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | SixLabors.ImageSharp 4 |
5 | SixLabors.ZlibStream 6 |

7 | 8 |
9 | 10 | [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 11 |
12 | 13 | A WIP fork of [zlib.managed](https://github.com/Elskom/zlib.managed) with target framework, API changes (hence fork) and performance improvements. 14 | 15 | The goal is to introduce as near-native performance as possible while implementing missing features from Zlib into the codebase. 16 | 17 | Targets netstandard1.3+ 18 | 19 | ## Why? 20 | 21 | DeflateStream in the .NET framework is a wrapper around the Intel fork of Zlib. 22 | This fork [sacrifices compression of sparse data](https://github.com/dotnet/runtime/issues/28235) for performance gains which results in [huge differences](https://github.com/SixLabors/ImageSharp/issues/1027) between the output size of certain images on Windows compared to other platforms. By producing a high performance managed implementation we can guarantee excellent cross platform image compression. 23 | 24 | ## Building the Project 25 | 26 | - Using [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) 27 | - Make sure you have the latest version installed 28 | - Make sure you have [the .NET 5 SDK](https://www.microsoft.com/net/core#windows) installed 29 | 30 | Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**: 31 | 32 | - [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) 33 | - [.NET Core](https://www.microsoft.com/net/core#linuxubuntu) 34 | 35 | To clone ZlibStream locally, click the "Clone in [YOUR_OS]" button above or run the following git commands: 36 | 37 | ```bash 38 | git clone https://github.com/SixLabors/ZlibStream 39 | ``` 40 | 41 | This repository contains [git submodules](https://blog.github.com/2016-02-01-working-with-submodules/). To add the submodules to the project, navigate to the repository root and type: 42 | 43 | ``` bash 44 | git submodule update --init --recursive 45 | ``` 46 | 47 | ### Benchmarks 48 | 49 | Benchmarks against the Canterbury corpus, a collection of files intended for use as a benchmark for testing lossless data compression algorithms can be found [here](benchmarks.md). 50 | 51 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/ZlibStream/ArrayExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | #if SUPPORTS_CORE_CLR 7 | using System.Runtime.InteropServices; 8 | #endif 9 | 10 | namespace SixLabors.ZlibStream 11 | { 12 | /// 13 | /// Helpers for working with the type. 14 | /// Adapted from 15 | /// 16 | internal static class ArrayExtensions 17 | { 18 | /// 19 | /// Returns a reference to an element at a specified index within a given array, with no bounds checks. 20 | /// 21 | /// The type of elements in the input array instance. 22 | /// The input array instance. 23 | /// The index of the element to retrieve within . 24 | /// A reference to the element within at the index specified by . 25 | /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid. 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | public static ref T DangerousGetReferenceAt(this T[] array, int i) 28 | { 29 | // TODO: NET5 WIll use MemoryMarshal.GetArrayDataReference 30 | #if SUPPORTS_CORE_CLR 31 | RawArrayData arrayData = Unsafe.As(array); 32 | ref T r0 = ref Unsafe.As(ref arrayData.Data); 33 | ref T ri = ref Unsafe.Add(ref r0, i); 34 | 35 | return ref ri; 36 | #else 37 | if ((uint)i < (uint)array.Length) 38 | { 39 | return ref array[i]; 40 | } 41 | 42 | unsafe 43 | { 44 | return ref Unsafe.AsRef(null); 45 | } 46 | #endif 47 | } 48 | 49 | #if SUPPORTS_CORE_CLR 50 | // Description taken from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285. 51 | // CLR arrays are laid out in memory as follows (multidimensional array bounds are optional): 52 | // [ sync block || pMethodTable || num components || MD array bounds || array data .. ] 53 | // ^ ^ ^ returned reference 54 | // | \-- ref Unsafe.As(array).Data 55 | // \-- array 56 | // The base size of an array includes all the fields before the array data, 57 | // including the sync block and method table. The reference to RawData.Data 58 | // points at the number of components, skipping over these two pointer-sized fields. 59 | [StructLayout(LayoutKind.Sequential)] 60 | private sealed class RawArrayData 61 | { 62 | #pragma warning disable CS0649 // Unassigned fields 63 | #pragma warning disable SA1401 // Fields should be private 64 | #pragma warning disable IDE1006 // Naming Styles 65 | public IntPtr Length; 66 | public byte Data; 67 | #pragma warning restore IDE1006 68 | #pragma warning restore CS0649 69 | #pragma warning restore SA1401 70 | } 71 | #endif 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/ZlibStream/CompressionLevel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace SixLabors.ZlibStream 5 | { 6 | /// 7 | /// Provides enumeration of compression levels. 8 | /// 9 | public enum CompressionLevel 10 | { 11 | /// 12 | /// The default compression level. Equivalent to . 13 | /// 14 | DefaultCompression = -1, 15 | 16 | /// 17 | /// Level 0. Equivalent to . 18 | /// 19 | Level0 = 0, 20 | 21 | /// 22 | /// No compression. Equivalent to . 23 | /// 24 | NoCompression = Level0, 25 | 26 | /// 27 | /// Level 1. Equivalent to . 28 | /// 29 | Level1 = 1, 30 | 31 | /// 32 | /// Best speed compression level. 33 | /// 34 | BestSpeed = Level1, 35 | 36 | /// 37 | /// Level 2. 38 | /// 39 | Level2 = 2, 40 | 41 | /// 42 | /// Level 3. 43 | /// 44 | Level3 = 3, 45 | 46 | /// 47 | /// Level 4. 48 | /// 49 | Level4 = 4, 50 | 51 | /// 52 | /// Level 5. 53 | /// 54 | Level5 = 5, 55 | 56 | /// 57 | /// Level 6. 58 | /// 59 | Level6 = 6, 60 | 61 | /// 62 | /// Level 7. 63 | /// 64 | Level7 = 7, 65 | 66 | /// 67 | /// Level 8. 68 | /// 69 | Level8 = 8, 70 | 71 | /// 72 | /// Level 9. Equivalent to . 73 | /// 74 | Level9 = 9, 75 | 76 | /// 77 | /// Best compression level. Equivalent to . 78 | /// 79 | BestCompression = Level9, 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/ZlibStream/CompressionState.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace SixLabors.ZlibStream 5 | { 6 | /// 7 | /// Compression state for zlib. 8 | /// 9 | internal enum CompressionState 10 | { 11 | /// 12 | /// Zlib version error. 13 | /// 14 | ZVERSIONERROR = -6, 15 | 16 | /// 17 | /// Buffer error. 18 | /// 19 | ZBUFERROR, 20 | 21 | /// 22 | /// Memory error. 23 | /// 24 | ZMEMERROR, 25 | 26 | /// 27 | /// Data error. 28 | /// 29 | ZDATAERROR, 30 | 31 | /// 32 | /// Stream error. 33 | /// 34 | ZSTREAMERROR, 35 | 36 | /// 37 | /// Some other error. 38 | /// 39 | ZERRNO, 40 | 41 | /// 42 | /// All is ok. 43 | /// 44 | ZOK, 45 | 46 | /// 47 | /// Stream ended early. 48 | /// 49 | ZSTREAMEND, 50 | 51 | /// 52 | /// Need compression dictionary. 53 | /// 54 | ZNEEDDICT, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ZlibStream/CompressionStrategy.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace SixLabors.ZlibStream 5 | { 6 | /// 7 | /// Provides enumeration for the various compression strategies. 8 | /// 9 | public enum CompressionStrategy 10 | { 11 | /// 12 | /// The default compression. Used for normal data. 13 | /// 14 | DefaultStrategy = 0, 15 | 16 | /// 17 | /// Filtered compression. Used for data produced by a filter (or predictor). 18 | /// 19 | Filtered = 1, 20 | 21 | /// 22 | /// Force Huffman encoding only (no string match). 23 | /// 24 | HuffmanOnly = 2, 25 | 26 | /// 27 | /// Run Length Encoded. Designed to be almost as fast as , 28 | /// but give better compression for PNG image data. 29 | /// 30 | Rle = 3, 31 | 32 | /// 33 | /// Prevents the use of dynamic Huffman codes, allowing for a simpler 34 | /// decoder for special applications. 35 | /// 36 | Fixed = 4 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ZlibStream/Deflate.Buffers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Buffers; 6 | 7 | namespace SixLabors.ZlibStream 8 | { 9 | /// 10 | /// Contains the buffers used during deflate. 11 | /// 12 | internal sealed unsafe partial class Deflate 13 | { 14 | /// 15 | /// Contains buffers whose lengths are defined by compile time constants. 16 | /// 17 | public sealed class FixedLengthBuffers : IDisposable 18 | { 19 | private bool isDisposed; 20 | 21 | // Number of codes at each bit length for an optimal tree 22 | private readonly ushort[] blCountBuffer; 23 | private MemoryHandle blCountHandle; 24 | 25 | // Heap used to build the Huffman trees 26 | private readonly int[] heapBuffer; 27 | private MemoryHandle heapHandle; 28 | 29 | // Depth of each subtree used as tie breaker for trees of equal frequency 30 | private readonly byte[] depthBuffer; 31 | private MemoryHandle depthHandle; 32 | 33 | /// 34 | /// Initializes a new instance of the class. 35 | /// 36 | public FixedLengthBuffers() 37 | { 38 | this.blCountBuffer = ArrayPool.Shared.Rent(MAXBITS + 1); 39 | this.blCountHandle = new Memory(this.blCountBuffer).Pin(); 40 | this.BlCountPointer = (ushort*)this.blCountHandle.Pointer; 41 | 42 | this.heapBuffer = ArrayPool.Shared.Rent((2 * LCODES) + 1); 43 | this.heapHandle = new Memory(this.heapBuffer).Pin(); 44 | this.HeapPointer = (int*)this.heapHandle.Pointer; 45 | 46 | this.depthBuffer = ArrayPool.Shared.Rent((2 * LCODES) + 1); 47 | this.depthHandle = new Memory(this.depthBuffer).Pin(); 48 | this.DepthPointer = (byte*)this.depthHandle.Pointer; 49 | } 50 | 51 | /// 52 | /// Gets number of codes at each bit length for an optimal tree. 53 | /// 54 | public ushort* BlCountPointer { get; } 55 | 56 | /// 57 | /// Gets the pointer to the heap used to build the Huffman trees. 58 | /// The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. 59 | /// The same heap array is used to build all trees. 60 | /// 61 | public int* HeapPointer { get; } 62 | 63 | /// 64 | /// Gets the depth of each subtree used as tie breaker for trees of equal frequency. 65 | /// 66 | public byte* DepthPointer { get; } 67 | 68 | /// 69 | public void Dispose() 70 | { 71 | if (!this.isDisposed) 72 | { 73 | this.depthHandle.Dispose(); 74 | ArrayPool.Shared.Return(this.depthBuffer); 75 | 76 | this.heapHandle.Dispose(); 77 | ArrayPool.Shared.Return(this.heapBuffer); 78 | 79 | this.blCountHandle.Dispose(); 80 | ArrayPool.Shared.Return(this.blCountBuffer); 81 | 82 | this.isDisposed = true; 83 | } 84 | } 85 | } 86 | 87 | /// 88 | /// Contains buffers whose lengths are defined by parameters passed 89 | /// to the containing instance. 90 | /// 91 | public sealed class DynamicLengthBuffers : IDisposable 92 | { 93 | private MemoryHandle windowHandle; 94 | 95 | // Link to older string with same hash index. To limit the size of this 96 | // array to 64K, this link is maintained only for the last 32K strings. 97 | // An index in this array is thus a window index modulo 32K. 98 | private readonly ushort[] prevBuffer; 99 | private MemoryHandle prevHandle; 100 | 101 | // Heads of the hash chains or NIL. 102 | private readonly ushort[] headBuffer; 103 | private MemoryHandle headHandle; 104 | 105 | private MemoryHandle pendingHandle; 106 | 107 | private bool isDisposed; 108 | 109 | /// 110 | /// Initializes a new instance of the class. 111 | /// 112 | /// The size of the sliding window. 113 | /// The size of the hash chain. 114 | /// The size of the pending buffer. 115 | public DynamicLengthBuffers(int wSize, int hashSize, int pendingSize) 116 | { 117 | this.WindowBuffer = ArrayPool.Shared.Rent(wSize * 2); 118 | this.windowHandle = new Memory(this.WindowBuffer).Pin(); 119 | this.WindowPointer = (byte*)this.windowHandle.Pointer; 120 | 121 | this.prevBuffer = ArrayPool.Shared.Rent(wSize); 122 | this.prevHandle = new Memory(this.prevBuffer).Pin(); 123 | this.PrevPointer = (ushort*)this.prevHandle.Pointer; 124 | 125 | this.headBuffer = ArrayPool.Shared.Rent(hashSize); 126 | this.headHandle = new Memory(this.headBuffer).Pin(); 127 | this.HeadPointer = (ushort*)this.headHandle.Pointer; 128 | 129 | // We overlay pending_buf and d_buf+l_buf. This works since the average 130 | // output size for (length,distance) codes is <= 24 bits. 131 | this.PendingSize = pendingSize; 132 | this.PendingBuffer = ArrayPool.Shared.Rent(pendingSize); 133 | this.pendingHandle = new Memory(this.PendingBuffer).Pin(); 134 | this.PendingPointer = (byte*)this.pendingHandle.Pointer; 135 | } 136 | 137 | /// 138 | /// Gets the sliding window. Input bytes are read into the second half of the window, 139 | /// and move to the first half later to keep a dictionary of at least wSize 140 | /// bytes. With this organization, matches are limited to a distance of 141 | /// wSize-MAX_MATCH bytes, but this ensures that IO is always 142 | /// performed with a length multiple of the block size. Also, it limits 143 | /// the window size to 64K, which is quite useful on MSDOS. 144 | /// To do: use the user input buffer as sliding window. 145 | /// 146 | public byte[] WindowBuffer { get; } 147 | 148 | /// 149 | /// Gets the pointer to the sliding window. 150 | /// 151 | public byte* WindowPointer { get; } 152 | 153 | /// 154 | /// Gets the pointer to the buffer of older strings with the same hash index. 155 | /// 156 | public ushort* PrevPointer { get; } 157 | 158 | /// 159 | /// Gets the pointer to the head of the hash chain. 160 | /// 161 | public ushort* HeadPointer { get; } 162 | 163 | /// 164 | /// Gets the output still pending. 165 | /// 166 | public byte[] PendingBuffer { get; } 167 | 168 | /// 169 | /// Gets the size of the pending buffer. 170 | /// 171 | public int PendingSize { get; } 172 | 173 | /// 174 | /// Gets the pointer to the pending output buffer. 175 | /// 176 | public byte* PendingPointer { get; } 177 | 178 | /// 179 | public void Dispose() 180 | { 181 | if (!this.isDisposed) 182 | { 183 | this.windowHandle.Dispose(); 184 | ArrayPool.Shared.Return(this.WindowBuffer); 185 | 186 | this.prevHandle.Dispose(); 187 | ArrayPool.Shared.Return(this.prevBuffer); 188 | 189 | this.headHandle.Dispose(); 190 | ArrayPool.Shared.Return(this.headBuffer); 191 | 192 | this.pendingHandle.Dispose(); 193 | ArrayPool.Shared.Return(this.PendingBuffer); 194 | 195 | this.isDisposed = true; 196 | } 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/ZlibStream/Deflate.Fast.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace SixLabors.ZlibStream 5 | { 6 | /// 7 | /// Contains the fast deflate implementation. 8 | /// 9 | internal sealed unsafe partial class Deflate 10 | { 11 | /// 12 | /// Compress as much as possible from the input stream, return the current 13 | /// block state. 14 | /// This function does not perform lazy evaluation of matches and inserts 15 | /// new strings in the dictionary only for unmatched strings or for ushort 16 | /// matches. It is used only for the fast compression options. 17 | /// 18 | /// The flush strategy. 19 | /// The . 20 | internal int DeflateFast(FlushMode flush) 21 | { 22 | int hash_head; // head of the hash chain 23 | bool bflush; // set if current block must be flushed 24 | 25 | byte* window = this.DynamicBuffers.WindowPointer; 26 | ushort* head = this.DynamicBuffers.HeadPointer; 27 | ushort* prev = this.DynamicBuffers.PrevPointer; 28 | 29 | while (true) 30 | { 31 | // Make sure that we always have enough lookahead, except 32 | // at the end of the input file. We need MAX_MATCH bytes 33 | // for the next match, plus MINMATCH bytes to insert the 34 | // string following the next match. 35 | if (this.lookahead < MINLOOKAHEAD) 36 | { 37 | this.Fill_window(); 38 | if (this.lookahead < MINLOOKAHEAD && flush == FlushMode.NoFlush) 39 | { 40 | return NeedMore; 41 | } 42 | 43 | if (this.lookahead == 0) 44 | { 45 | break; // flush the current block 46 | } 47 | } 48 | 49 | // Insert the string window[strstart .. strstart+2] in the 50 | // dictionary, and set hash_head to the head of the hash chain: 51 | hash_head = 0; 52 | if (this.lookahead >= MINMATCH) 53 | { 54 | hash_head = this.InsertString(prev, head, window, this.strStart); 55 | } 56 | 57 | // Find the longest match, discarding those <= prev_length. 58 | // At this point we have always match_length < MINMATCH 59 | if (hash_head != 0 && (this.strStart - hash_head) <= this.wSize - MINLOOKAHEAD) 60 | { 61 | // To simplify the code, we prevent matches with the string 62 | // of window index 0 (in particular we have to avoid a match 63 | // of the string with itself at the start of the input file). 64 | if (this.Strategy != CompressionStrategy.HuffmanOnly) 65 | { 66 | this.matchLength = this.Longest_match(hash_head); 67 | 68 | // longest_match() sets match_start 69 | } 70 | } 71 | 72 | if (this.matchLength >= MINMATCH) 73 | { 74 | // check_match(strstart, match_start, match_length); 75 | bflush = this.Tr_tally_dist(this.strStart - this.matchStart, this.matchLength - MINMATCH); 76 | 77 | this.lookahead -= this.matchLength; 78 | 79 | // Insert new strings in the hash table only if the match length 80 | // is not too large. This saves time but degrades compression. 81 | if (this.matchLength <= this.maxLazyMatch && this.lookahead >= MINMATCH) 82 | { 83 | this.matchLength--; // string at strstart already in hash table 84 | do 85 | { 86 | this.strStart++; 87 | this.InsertString(prev, head, window, this.strStart); 88 | 89 | // strstart never exceeds WSIZE-MAX_MATCH, so there are 90 | // always MINMATCH bytes ahead. 91 | } 92 | while (--this.matchLength != 0); 93 | this.strStart++; 94 | } 95 | else 96 | { 97 | this.strStart += this.matchLength; 98 | this.matchLength = 0; 99 | 100 | this.UpdateHash(window[this.strStart + 1]); 101 | 102 | // If lookahead < MINMATCH, insH is garbage, but it does not 103 | // matter since it will be recomputed at next deflate call. 104 | } 105 | } 106 | else 107 | { 108 | // No match, output a literal byte 109 | bflush = this.Tr_tally_lit(window[this.strStart]); 110 | this.lookahead--; 111 | this.strStart++; 112 | } 113 | 114 | if (bflush) 115 | { 116 | this.Flush_block_only(false); 117 | if (this.strm.AvailableOut == 0) 118 | { 119 | return NeedMore; 120 | } 121 | } 122 | } 123 | 124 | this.Flush_block_only(flush == FlushMode.Finish); 125 | return this.strm.AvailableOut == 0 126 | ? flush == FlushMode.Finish ? FinishStarted : NeedMore 127 | : flush == FlushMode.Finish ? FinishDone : BlockDone; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/ZlibStream/Deflate.Intrinsics.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Numerics; 5 | using System.Runtime.CompilerServices; 6 | #if SUPPORTS_RUNTIME_INTRINSICS 7 | using System.Runtime.Intrinsics; 8 | using System.Runtime.Intrinsics.X86; 9 | #endif 10 | 11 | namespace SixLabors.ZlibStream 12 | { 13 | /// 14 | /// Contains methods backed with intrinsics. 15 | /// 16 | internal sealed unsafe partial class Deflate 17 | { 18 | [MethodImpl(InliningOptions.ShortMethod)] 19 | private static int Compare256(byte* src0, byte* src1) 20 | { 21 | #if SUPPORTS_RUNTIME_INTRINSICS 22 | if (Avx2.IsSupported) 23 | { 24 | return Compare256Avx2(src0, src1); 25 | } 26 | else if (Sse2.IsSupported) 27 | { 28 | return Compare256Sse2(src0, src1); 29 | } 30 | else 31 | #endif 32 | { 33 | return Compare256Scalar(src0, src1); 34 | } 35 | } 36 | 37 | #if SUPPORTS_RUNTIME_INTRINSICS 38 | [MethodImpl(InliningOptions.ShortMethod)] 39 | private static int Compare256Avx2(byte* src0, byte* src1) 40 | { 41 | int len = 0; 42 | 43 | do 44 | { 45 | Vector256 ymm_src0 = Avx.LoadVector256(src0); 46 | Vector256 ymm_src1 = Avx.LoadVector256(src1); 47 | 48 | // non-identical bytes = 00, identical bytes = FF 49 | Vector256 ymm_cmp = Avx2.CompareEqual(ymm_src0, ymm_src1); 50 | 51 | int mask = Avx2.MoveMask(ymm_cmp); 52 | if ((uint)mask != uint.MaxValue) 53 | { 54 | // Invert bits so identical = 0 55 | int match_byte = BitOperations.TrailingZeroCount(~mask); 56 | return len + match_byte; 57 | } 58 | 59 | ymm_src0 = Avx.LoadVector256(src0 + 32); 60 | ymm_src1 = Avx.LoadVector256(src1 + 32); 61 | ymm_cmp = Avx2.CompareEqual(ymm_src0, ymm_src1); 62 | 63 | mask = Avx2.MoveMask(ymm_cmp); 64 | if ((uint)mask != uint.MaxValue) 65 | { 66 | int match_byte = BitOperations.TrailingZeroCount(~mask); 67 | return len + 32 + match_byte; 68 | } 69 | 70 | src0 += 64; 71 | src1 += 64; 72 | len += 64; 73 | } 74 | while (len < 256); 75 | 76 | return 256; 77 | } 78 | 79 | [MethodImpl(InliningOptions.ShortMethod)] 80 | private static int Compare256Sse2(byte* src0, byte* src1) 81 | { 82 | int len = 0; 83 | 84 | do 85 | { 86 | Vector128 ymm_src0 = Sse2.LoadVector128(src0); 87 | Vector128 ymm_src1 = Sse2.LoadVector128(src1); 88 | 89 | // non-identical bytes = 00, identical bytes = FF 90 | Vector128 ymm_cmp = Sse2.CompareEqual(ymm_src0, ymm_src1); 91 | 92 | int mask = Sse2.MoveMask(ymm_cmp); 93 | if ((ushort)mask != ushort.MaxValue) 94 | { 95 | // Invert bits so identical = 0 96 | int match_byte = BitOperations.TrailingZeroCount(~mask); 97 | return len + match_byte; 98 | } 99 | 100 | ymm_src0 = Sse2.LoadVector128(src0 + 16); 101 | ymm_src1 = Sse2.LoadVector128(src1 + 16); 102 | ymm_cmp = Sse2.CompareEqual(ymm_src0, ymm_src1); 103 | 104 | mask = Sse2.MoveMask(ymm_cmp); 105 | if ((uint)mask != uint.MaxValue) 106 | { 107 | int match_byte = BitOperations.TrailingZeroCount(~mask); 108 | return len + 16 + match_byte; 109 | } 110 | 111 | src0 += 32; 112 | src1 += 32; 113 | len += 32; 114 | } 115 | while (len < 256); 116 | 117 | return 256; 118 | } 119 | #endif 120 | 121 | [MethodImpl(InliningOptions.ShortMethod)] 122 | private static int Compare256Scalar(byte* src0, byte* src1) 123 | { 124 | int len = 0; 125 | do 126 | { 127 | if (*(ushort*)src0 != *(ushort*)src1) 128 | { 129 | return len + ((*src0 == *src1) ? 1 : 0); 130 | } 131 | 132 | src0 += 2; 133 | src1 += 2; 134 | len += 2; 135 | if (*(ushort*)src0 != *(ushort*)src1) 136 | { 137 | return len + ((*src0 == *src1) ? 1 : 0); 138 | } 139 | 140 | src0 += 2; 141 | src1 += 2; 142 | len += 2; 143 | if (*(ushort*)src0 != *(ushort*)src1) 144 | { 145 | return len + ((*src0 == *src1) ? 1 : 0); 146 | } 147 | 148 | src0 += 2; 149 | src1 += 2; 150 | len += 2; 151 | if (*(ushort*)src0 != *(ushort*)src1) 152 | { 153 | return len + ((*src0 == *src1) ? 1 : 0); 154 | } 155 | 156 | src0 += 2; 157 | src1 += 2; 158 | len += 2; 159 | } 160 | while (len < 256); 161 | return 256; 162 | } 163 | 164 | /// 165 | /// Slide the hash table (could be avoided with 32 bit values 166 | /// at the expense of memory usage). We slide even when level == 0 167 | /// to keep the hash table consistent if we switch back to level > 0 168 | /// later. (Using level 0 permanently is not an optimal usage of 169 | /// zlib, so we don't care about this pathological case.) 170 | /// 171 | /// Heads of the hash chains or NIL. 172 | /// Link to older string with same hash index. 173 | [MethodImpl(InliningOptions.ShortMethod)] 174 | private void SlideHash(ushort* head, ushort* prev) 175 | { 176 | #if SUPPORTS_RUNTIME_INTRINSICS 177 | if (Avx2.IsSupported) 178 | { 179 | this.SlideHashAvx2(head, prev); 180 | } 181 | else if (Sse2.IsSupported) 182 | { 183 | this.SlideHashSse2(head, prev); 184 | } 185 | else 186 | #endif 187 | { 188 | this.SlideHashScalar(head, prev); 189 | } 190 | } 191 | 192 | #if SUPPORTS_RUNTIME_INTRINSICS 193 | [MethodImpl(InliningOptions.ShortMethod)] 194 | private void SlideHashAvx2(ushort* head, ushort* prev) 195 | { 196 | ushort wsize = (ushort)this.wSize; 197 | var xmm_wsize = Vector256.Create(wsize); 198 | 199 | int n = this.hashSize; 200 | ushort* p = &head[n] - 16; 201 | do 202 | { 203 | Vector256 value = Avx.LoadVector256(p); 204 | Vector256 result = Avx2.SubtractSaturate(value, xmm_wsize); 205 | Avx.Store(p, result); 206 | 207 | p -= 16; 208 | n -= 16; 209 | } 210 | while (n > 0); 211 | 212 | n = wsize; 213 | p = &prev[n] - 16; 214 | do 215 | { 216 | Vector256 value = Avx.LoadVector256(p); 217 | Vector256 result = Avx2.SubtractSaturate(value, xmm_wsize); 218 | Avx.Store(p, result); 219 | 220 | p -= 16; 221 | n -= 16; 222 | } 223 | while (n > 0); 224 | } 225 | 226 | [MethodImpl(InliningOptions.ShortMethod)] 227 | private void SlideHashSse2(ushort* head, ushort* prev) 228 | { 229 | ushort wsize = (ushort)this.wSize; 230 | var xmm_wsize = Vector128.Create(wsize); 231 | 232 | int n = this.hashSize; 233 | ushort* p = &head[n] - 8; 234 | do 235 | { 236 | Vector128 value = Sse2.LoadVector128(p); 237 | Vector128 result = Sse2.SubtractSaturate(value, xmm_wsize); 238 | Sse2.Store(p, result); 239 | 240 | p -= 8; 241 | n -= 8; 242 | } 243 | while (n > 0); 244 | 245 | n = wsize; 246 | p = &prev[n] - 8; 247 | do 248 | { 249 | Vector128 value = Sse2.LoadVector128(p); 250 | Vector128 result = Sse2.SubtractSaturate(value, xmm_wsize); 251 | Sse2.Store(p, result); 252 | 253 | p -= 8; 254 | n -= 8; 255 | } 256 | while (n > 0); 257 | } 258 | #endif 259 | 260 | [MethodImpl(InliningOptions.ShortMethod)] 261 | private void SlideHashScalar(ushort* head, ushort* prev) 262 | { 263 | int wsize = this.wSize; 264 | int n = this.hashSize; 265 | int p = n; 266 | int m; 267 | do 268 | { 269 | m = head[--p]; 270 | head[p] = (ushort)(m >= wsize ? (m - wsize) : 0); 271 | } 272 | while (--n != 0); 273 | 274 | n = wsize; 275 | p = n; 276 | do 277 | { 278 | m = prev[--p]; 279 | prev[p] = (ushort)(m >= wsize ? (m - wsize) : 0); 280 | 281 | // If n is not on any hash chain, prev[n] is garbage but 282 | // its value will never be used. 283 | } 284 | while (--n != 0); 285 | } 286 | 287 | /// 288 | /// Update a hash value with the given input byte 289 | /// IN assertion: all calls to UPDATE_HASH are made with consecutive input 290 | /// characters, so that a running hash key can be computed from the previous 291 | /// key instead of complete recalculation each time. 292 | /// 293 | /// The input byte. 294 | [MethodImpl(InliningOptions.ShortMethod)] 295 | private uint UpdateHash(uint val) 296 | { 297 | #if SUPPORTS_RUNTIME_INTRINSICS 298 | if (Sse42.IsSupported) 299 | { 300 | return Sse42.Crc32(0U, val); 301 | } 302 | else 303 | #endif 304 | { 305 | return (val * 2654435761U) >> 16; 306 | } 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/ZlibStream/Deflate.Quick.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace SixLabors.ZlibStream 7 | { 8 | /// 9 | /// Contains the quick deflate implementation. 10 | /// 11 | internal sealed unsafe partial class Deflate 12 | { 13 | /// 14 | /// The deflate_quick deflate strategy, designed to be used when cycles are 15 | /// at a premium. 16 | /// 17 | /// The flush strategy. 18 | /// The . 19 | internal int DeflateQuick(FlushMode flush) 20 | { 21 | int hash_head; // head of the hash chain 22 | int dist; 23 | int matchLen; 24 | bool last; 25 | 26 | byte* window = this.DynamicBuffers.WindowPointer; 27 | ushort* head = this.DynamicBuffers.HeadPointer; 28 | ushort* prev = this.DynamicBuffers.PrevPointer; 29 | 30 | fixed (Trees.CodeData* ltree = &Trees.StaticLTreeDesc.GetCodeDataReference()) 31 | fixed (Trees.CodeData* dtree = &Trees.StaticDTreeDesc.GetCodeDataReference()) 32 | { 33 | if (!this.blockOpen && this.lookahead > 0) 34 | { 35 | // Start new block when we have lookahead data, so that if no 36 | // input data is given an empty block will not be written. 37 | last = flush == FlushMode.Finish; 38 | this.QuickStartBlock(last); 39 | } 40 | 41 | int pendingBufferSize = this.DynamicBuffers.PendingSize; 42 | do 43 | { 44 | if (this.Pending + ((BITBUFSIZE + 7) >> 3) >= pendingBufferSize) 45 | { 46 | this.Flush_pending(this.strm); 47 | if (this.strm.AvailableIn == 0 && flush != FlushMode.Finish) 48 | { 49 | // Break to emit end block and return need_more 50 | break; 51 | } 52 | } 53 | 54 | if (this.lookahead < MINLOOKAHEAD) 55 | { 56 | this.Fill_window(); 57 | if (this.lookahead < MINLOOKAHEAD && flush == FlushMode.NoFlush) 58 | { 59 | // Always emit end block, in case next call is with Z_FINISH 60 | // and we need to emit start of last block 61 | this.QuickEndBlock(ltree, false); 62 | return NeedMore; 63 | } 64 | 65 | if (this.lookahead == 0) 66 | { 67 | break; 68 | } 69 | 70 | if (!this.blockOpen) 71 | { 72 | // Start new block when we have lookahead data, so that if no 73 | // input data is given an empty block will not be written. 74 | last = flush == FlushMode.Finish; 75 | this.QuickStartBlock(last); 76 | } 77 | } 78 | 79 | if (this.lookahead >= MINMATCH) 80 | { 81 | hash_head = this.InsertString(prev, head, window, this.strStart); 82 | dist = this.strStart - hash_head; 83 | 84 | if (dist > 0 && dist < this.wSize - MINLOOKAHEAD) 85 | { 86 | matchLen = Compare258(window + this.strStart, window + hash_head); 87 | 88 | if (matchLen >= MINMATCH) 89 | { 90 | if (matchLen > this.lookahead) 91 | { 92 | matchLen = this.lookahead; 93 | } 94 | 95 | Trees.Tr_emit_distance(this, ltree, dtree, matchLen - MINMATCH, dist); 96 | this.lookahead -= matchLen; 97 | this.strStart += matchLen; 98 | continue; 99 | } 100 | } 101 | } 102 | 103 | this.Send_code(window[this.strStart], ltree); // send a literal byte 104 | this.strStart++; 105 | this.lookahead--; 106 | } 107 | while (this.strm.AvailableOut != 0); 108 | 109 | last = flush == FlushMode.Finish; 110 | this.QuickEndBlock(ltree, last); 111 | this.Flush_pending(this.strm); 112 | 113 | if (last) 114 | { 115 | return this.strm.AvailableOut == 0 116 | ? this.strm.AvailableIn == 0 ? FinishStarted : NeedMore 117 | : FinishDone; 118 | } 119 | 120 | return BlockDone; 121 | } 122 | } 123 | 124 | [MethodImpl(InliningOptions.ShortMethod)] 125 | private static int Compare258(byte* src0, byte* src1) 126 | { 127 | if (*(ushort*)src0 != *(ushort*)src1) 128 | { 129 | return (*src0 == *src1) ? 1 : 0; 130 | } 131 | 132 | return Compare256(src0 + 2, src1 + 2) + 2; 133 | } 134 | 135 | [MethodImpl(InliningOptions.ShortMethod)] 136 | private void QuickStartBlock(bool last) 137 | { 138 | Trees.Tr_emit_tree(this, STATICTREES, last); 139 | this.blockStart = this.strStart; 140 | this.blockOpen = true; 141 | } 142 | 143 | [MethodImpl(InliningOptions.ShortMethod)] 144 | private void QuickEndBlock(Trees.CodeData* lTree, bool last) 145 | { 146 | if (this.blockOpen) 147 | { 148 | Trees.Tr_emit_end_block(this, lTree, last); 149 | this.blockStart = this.strStart; 150 | this.blockOpen = false; 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/ZlibStream/Deflate.Rle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace SixLabors.ZlibStream 5 | { 6 | /// 7 | /// Contains the RLE deflate implementation. 8 | /// 9 | internal sealed unsafe partial class Deflate 10 | { 11 | /// 12 | /// For Z_RLE, simply look for runs of bytes, generate matches only of distance 13 | /// one.Do not maintain a hash table. (It will be regenerated if this run of 14 | /// deflate switches away from Z_RLE. 15 | /// 16 | /// The flush strategy. 17 | /// The . 18 | internal int DeflateRle(FlushMode flush) 19 | { 20 | bool bflush; // set if current block must be flushed 21 | int prev; // byte at distance one to match 22 | byte* scan; // scan goes up to strend for length of run 23 | byte* strend; 24 | byte* window = this.DynamicBuffers.WindowPointer; 25 | 26 | while (true) 27 | { 28 | // Make sure that we always have enough lookahead, except 29 | // at the end of the input file. We need MAX_MATCH bytes 30 | // for the longest run, plus one for the unrolled loop. 31 | if (this.lookahead <= MAXMATCH) 32 | { 33 | this.Fill_window(); 34 | if (this.lookahead <= MAXMATCH && flush == FlushMode.NoFlush) 35 | { 36 | return NeedMore; 37 | } 38 | } 39 | 40 | if (this.lookahead == 0) 41 | { 42 | break; 43 | } 44 | 45 | // See how many times the previous byte repeats 46 | this.matchLength = 0; 47 | if (this.lookahead >= MINMATCH && this.strStart > 0) 48 | { 49 | scan = window + this.strStart - 1; 50 | prev = *scan; 51 | 52 | if (prev == *++scan && prev == *++scan && prev == *++scan) 53 | { 54 | strend = window + this.strStart + MAXMATCH; 55 | do 56 | { 57 | } 58 | while (prev == *++scan && prev == *++scan 59 | && prev == *++scan && prev == *++scan 60 | && prev == *++scan && prev == *++scan 61 | && prev == *++scan && prev == *++scan 62 | && scan < strend); 63 | 64 | this.matchLength = MAXMATCH - (int)(strend - scan); 65 | 66 | if (this.matchLength > this.lookahead) 67 | { 68 | this.matchLength = this.lookahead; 69 | } 70 | } 71 | } 72 | 73 | // Emit match if have run of MIN_MATCH or longer, else emit literal 74 | if (this.matchLength >= MINMATCH) 75 | { 76 | bflush = this.Tr_tally_dist(1, this.matchLength - MINMATCH); 77 | 78 | this.lookahead -= this.matchLength; 79 | this.strStart += this.matchLength; 80 | this.matchLength = 0; 81 | } 82 | else 83 | { 84 | // No match, output a literal byte 85 | bflush = this.Tr_tally_lit(window[this.strStart]); 86 | this.lookahead--; 87 | this.strStart++; 88 | } 89 | 90 | if (bflush) 91 | { 92 | this.Flush_block_only(false); 93 | if (this.strm.AvailableOut == 0) 94 | { 95 | return NeedMore; 96 | } 97 | } 98 | } 99 | 100 | this.Flush_block_only(flush == FlushMode.Finish); 101 | return this.strm.AvailableOut == 0 102 | ? flush == FlushMode.Finish ? FinishStarted : NeedMore 103 | : flush == FlushMode.Finish ? FinishDone : BlockDone; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/ZlibStream/Deflate.Slow.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace SixLabors.ZlibStream 5 | { 6 | /// 7 | /// Contains the slow deflate implementation. 8 | /// 9 | internal sealed unsafe partial class Deflate 10 | { 11 | /// 12 | /// Same as fast, but achieves better compression. We use a lazy 13 | /// evaluation for matches: a match is finally adopted only if there is 14 | /// no better match at the next window position. 15 | /// 16 | /// The flush strategy. 17 | /// The . 18 | private int DeflateSlow(FlushMode flush) 19 | { 20 | int hash_head = 0; // head of hash chain 21 | bool bflush; // set if current block must be flushed 22 | 23 | byte* window = this.DynamicBuffers.WindowPointer; 24 | ushort* head = this.DynamicBuffers.HeadPointer; 25 | ushort* prev = this.DynamicBuffers.PrevPointer; 26 | 27 | // Process the input block. 28 | while (true) 29 | { 30 | // Make sure that we always have enough lookahead, except 31 | // at the end of the input file. We need MAX_MATCH bytes 32 | // for the next match, plus MINMATCH bytes to insert the 33 | // string following the next match. 34 | if (this.lookahead < MINLOOKAHEAD) 35 | { 36 | this.Fill_window(); 37 | if (this.lookahead < MINLOOKAHEAD && flush == FlushMode.NoFlush) 38 | { 39 | return NeedMore; 40 | } 41 | 42 | if (this.lookahead == 0) 43 | { 44 | break; // flush the current block 45 | } 46 | } 47 | 48 | // Insert the string window[strstart .. strstart+2] in the 49 | // dictionary, and set hash_head to the head of the hash chain: 50 | if (this.lookahead >= MINMATCH) 51 | { 52 | hash_head = this.InsertString(prev, head, window, this.strStart); 53 | } 54 | 55 | // Find the longest match, discarding those <= prev_length. 56 | this.prevLength = this.matchLength; 57 | this.prevMatch = this.matchStart; 58 | this.matchLength = MINMATCH - 1; 59 | 60 | if (hash_head != 0 && this.prevLength < this.maxLazyMatch 61 | && this.strStart - hash_head <= this.wSize - MINLOOKAHEAD) 62 | { 63 | // To simplify the code, we prevent matches with the string 64 | // of window index 0 (in particular we have to avoid a match 65 | // of the string with itself at the start of the input file). 66 | if (this.Strategy != CompressionStrategy.HuffmanOnly) 67 | { 68 | this.matchLength = this.Longest_match(hash_head); 69 | } 70 | 71 | // longest_match() sets match_start 72 | if (this.matchLength <= 5 && (this.Strategy == CompressionStrategy.Filtered 73 | || (this.matchLength == MINMATCH && this.strStart - this.matchStart > 4096))) 74 | { 75 | // If prev_match is also MINMATCH, match_start is garbage 76 | // but we will ignore the current match anyway. 77 | this.matchLength = MINMATCH - 1; 78 | } 79 | } 80 | 81 | // If there was a match at the previous step and the current 82 | // match is not better, output the previous match: 83 | if (this.prevLength >= MINMATCH && this.matchLength <= this.prevLength) 84 | { 85 | int max_insert = this.strStart + this.lookahead - MINMATCH; 86 | 87 | // Do not insert strings in hash table beyond this. 88 | 89 | // check_match(strstart-1, prev_match, prev_length); 90 | bflush = this.Tr_tally_dist(this.strStart - 1 - this.prevMatch, this.prevLength - MINMATCH); 91 | 92 | // Insert in hash table all strings up to the end of the match. 93 | // strstart-1 and strstart are already inserted. If there is not 94 | // enough lookahead, the last two strings are not inserted in 95 | // the hash table. 96 | this.lookahead -= this.prevLength - 1; 97 | this.prevLength -= 2; 98 | do 99 | { 100 | if (++this.strStart <= max_insert) 101 | { 102 | hash_head = this.InsertString(prev, head, window, this.strStart); 103 | } 104 | } 105 | while (--this.prevLength != 0); 106 | this.matchAvailable = 0; 107 | this.matchLength = MINMATCH - 1; 108 | this.strStart++; 109 | 110 | if (bflush) 111 | { 112 | this.Flush_block_only(false); 113 | if (this.strm.AvailableOut == 0) 114 | { 115 | return NeedMore; 116 | } 117 | } 118 | } 119 | else if (this.matchAvailable != 0) 120 | { 121 | // If there was no match at the previous position, output a 122 | // single literal. If there was a match but the current match 123 | // is longer, truncate the previous match to a single literal. 124 | bflush = this.Tr_tally_lit(window[this.strStart - 1]); 125 | 126 | if (bflush) 127 | { 128 | this.Flush_block_only(false); 129 | } 130 | 131 | this.strStart++; 132 | this.lookahead--; 133 | if (this.strm.AvailableOut == 0) 134 | { 135 | return NeedMore; 136 | } 137 | } 138 | else 139 | { 140 | // There is no previous match to compare with, wait for 141 | // the next step to decide. 142 | this.matchAvailable = 1; 143 | this.strStart++; 144 | this.lookahead--; 145 | } 146 | } 147 | 148 | if (this.matchAvailable != 0) 149 | { 150 | _ = this.Tr_tally_lit(window[this.strStart - 1]); 151 | this.matchAvailable = 0; 152 | } 153 | 154 | this.Flush_block_only(flush == FlushMode.Finish); 155 | 156 | return this.strm.AvailableOut == 0 157 | ? flush == FlushMode.Finish ? FinishStarted : NeedMore 158 | : flush == FlushMode.Finish ? FinishDone : BlockDone; 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/ZlibStream/Deflate.Stored.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | 6 | namespace SixLabors.ZlibStream 7 | { 8 | /// 9 | /// Contains the stored deflate implementation. 10 | /// 11 | internal sealed unsafe partial class Deflate 12 | { 13 | /// 14 | /// Copy without compression as much as possible from the input stream, return 15 | /// the current block state. 16 | /// This function does not insert new strings in the dictionary since 17 | /// uncompressible data is probably not useful. This function is used 18 | /// only for the level=0 compression option. 19 | /// NOTE: this function should be optimized to avoid extra copying from 20 | /// window to pending_buf. 21 | /// 22 | /// The flush strategy. 23 | /// The . 24 | private int DeflateStored(FlushMode flush) 25 | { 26 | // Smallest worthy block size when not flushing or finishing. By default 27 | // this is 32K.This can be as small as 507 bytes for memLevel == 1., pending_buf is limited 28 | // to pending_buf_size, and each stored block has a 5 byte header: 29 | int max_block_size = Math.Min(this.DynamicBuffers.PendingSize - 5, this.wSize); 30 | int max_start; 31 | 32 | // Copy as much as possible from input to output: 33 | while (true) 34 | { 35 | // Fill the window as much as possible: 36 | if (this.lookahead <= 1) 37 | { 38 | this.Fill_window(); 39 | if (this.lookahead == 0 && flush == FlushMode.NoFlush) 40 | { 41 | return NeedMore; 42 | } 43 | 44 | if (this.lookahead == 0) 45 | { 46 | break; // flush the current block 47 | } 48 | } 49 | 50 | this.strStart += this.lookahead; 51 | this.lookahead = 0; 52 | 53 | // Emit a stored block if pending_buf will be full: 54 | max_start = this.blockStart + max_block_size; 55 | if (this.strStart == 0 || this.strStart >= max_start) 56 | { 57 | // strstart == 0 is possible when wraparound on 16-bit machine 58 | this.lookahead = this.strStart - max_start; 59 | this.strStart = max_start; 60 | 61 | this.Flush_block_only(false); 62 | if (this.strm.AvailableOut == 0) 63 | { 64 | return NeedMore; 65 | } 66 | } 67 | 68 | // Flush if we may have to slide, otherwise block_start may become 69 | // negative and the data will be gone: 70 | if (this.strStart - this.blockStart >= this.wSize - MINLOOKAHEAD) 71 | { 72 | this.Flush_block_only(false); 73 | if (this.strm.AvailableOut == 0) 74 | { 75 | return NeedMore; 76 | } 77 | } 78 | } 79 | 80 | this.Flush_block_only(flush == FlushMode.Finish); 81 | return this.strm.AvailableOut == 0 ? (flush == FlushMode.Finish) 82 | ? FinishStarted 83 | : NeedMore : flush == FlushMode.Finish ? FinishDone : BlockDone; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/ZlibStream/FlushMode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace SixLabors.ZlibStream 5 | { 6 | /// 7 | /// Provides enumeration of flushing modes. 8 | /// 9 | public enum FlushMode 10 | { 11 | /// 12 | /// No flush. 13 | /// 14 | NoFlush = 0, 15 | 16 | /// 17 | /// Partial flush. 18 | /// 19 | PartialFlush = 1, 20 | 21 | /// 22 | /// Sync flush. 23 | /// 24 | SyncFlush = 2, 25 | 26 | /// 27 | /// Full flush. 28 | /// 29 | FullFlush = 3, 30 | 31 | /// 32 | /// Finish compression or decompression. 33 | /// 34 | Finish = 4, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ZlibStream/InliningOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace SixLabors.ZlibStream 7 | { 8 | /// 9 | /// Global inlining options. Helps temporarily disable inlining for better profiler output. 10 | /// 11 | internal static class InliningOptions 12 | { 13 | #if PROFILING 14 | public const MethodImplOptions HotPath = MethodImplOptions.NoInlining; 15 | public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; 16 | #else 17 | #if SUPPORTS_HOTPATH 18 | public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization; 19 | #else 20 | public const MethodImplOptions HotPath = MethodImplOptions.AggressiveInlining; 21 | #endif 22 | public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; 23 | #endif 24 | public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ZlibStream/SerializableAttribute.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | // Copyright (c) Six Labors and contributors. 4 | // See LICENSE for more details. 5 | 6 | #if !SUPPORTS_SERIALIZATION 7 | namespace System 8 | { 9 | /// 10 | /// Indicates that a class can be serialized. This class cannot be inherited. 11 | /// 12 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Delegate, Inherited = false)] 13 | internal sealed class SerializableAttribute : Attribute 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public SerializableAttribute() { } 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /src/ZlibStream/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | #if !SUPPORTS_SPAN_STREAM 5 | using System; 6 | using System.Buffers; 7 | using System.IO; 8 | 9 | namespace SixLabors.ZlibStream 10 | { 11 | /// 12 | /// Extension methods to for that allow reading and writing to a span. 13 | /// 14 | internal static class StreamExtensions 15 | { 16 | // This is a port of the CoreFX implementation and is MIT Licensed: 17 | // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742 18 | public static int Read(this Stream stream, Span buffer) 19 | { 20 | byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); 21 | try 22 | { 23 | int numRead = stream.Read(sharedBuffer, 0, buffer.Length); 24 | if ((uint)numRead > (uint)buffer.Length) 25 | { 26 | throw new IOException("Stream was too long."); 27 | } 28 | 29 | new Span(sharedBuffer, 0, numRead).CopyTo(buffer); 30 | return numRead; 31 | } 32 | finally 33 | { 34 | ArrayPool.Shared.Return(sharedBuffer); 35 | } 36 | } 37 | 38 | // This is a port of the CoreFX implementation and is MIT Licensed: 39 | // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L775 40 | public static void Write(this Stream stream, ReadOnlySpan buffer) 41 | { 42 | byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); 43 | try 44 | { 45 | buffer.CopyTo(sharedBuffer); 46 | stream.Write(sharedBuffer, 0, buffer.Length); 47 | } 48 | finally 49 | { 50 | ArrayPool.Shared.Return(sharedBuffer); 51 | } 52 | } 53 | } 54 | } 55 | #endif 56 | -------------------------------------------------------------------------------- /src/ZlibStream/ThrowHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace SixLabors.ZlibStream 9 | { 10 | [DebuggerStepThrough] 11 | internal static class ThrowHelper 12 | { 13 | [MethodImpl(InliningOptions.ColdPath)] 14 | public static void ThrowArgumentNullException(string paramName) 15 | => throw new ArgumentNullException(paramName); 16 | 17 | [MethodImpl(InliningOptions.ColdPath)] 18 | public static void ThrowArgumentRangeException(string paramName) 19 | => throw new ArgumentOutOfRangeException(paramName); 20 | 21 | [MethodImpl(InliningOptions.ColdPath)] 22 | public static void ThrowCompressionException(bool compressing, string message) 23 | => throw new ZlibStreamException((compressing ? "de" : "in") + "flating: " + message); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ZlibStream/Trees.Dynamic.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Buffers; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace SixLabors.ZlibStream 9 | { 10 | internal sealed unsafe partial class Trees 11 | { 12 | /// 13 | /// A dynamic tree descriptor. 14 | /// 15 | public sealed class DynamicTreeDesc : IDisposable 16 | { 17 | private readonly CodeData[] dynTreeBuffer; 18 | private MemoryHandle dynTreeHandle; 19 | private bool isDisposed; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// The size of the tree. 25 | public DynamicTreeDesc(int size) 26 | { 27 | this.dynTreeBuffer = ArrayPool.Shared.Rent(size); 28 | this.dynTreeHandle = new Memory(this.dynTreeBuffer).Pin(); 29 | this.Pointer = (CodeData*)this.dynTreeHandle.Pointer; 30 | } 31 | 32 | /// 33 | /// Gets the pointer to the tree code data. 34 | /// 35 | public CodeData* Pointer 36 | { 37 | [MethodImpl(InliningOptions.ShortMethod)] 38 | get; 39 | } 40 | 41 | /// 42 | /// Gets or sets the largest code with non zero frequency. 43 | /// 44 | public int MaxCode { get; set; } 45 | 46 | public ref CodeData this[int i] 47 | { 48 | [MethodImpl(InliningOptions.ShortMethod)] 49 | get { return ref this.Pointer[i]; } 50 | } 51 | 52 | /// 53 | public void Dispose() => this.Dispose(true); 54 | 55 | private void Dispose(bool disposing) 56 | { 57 | if (!this.isDisposed) 58 | { 59 | if (disposing) 60 | { 61 | this.dynTreeHandle.Dispose(); 62 | ArrayPool.Shared.Return(this.dynTreeBuffer); 63 | } 64 | 65 | this.isDisposed = true; 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/ZlibStream/Trees.Static.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace SixLabors.ZlibStream 9 | { 10 | internal sealed unsafe partial class Trees 11 | { 12 | /// 13 | /// Gets the static literal tree. Since the bit lengths are imposed, there is no 14 | /// need for the L_CODES extra codes used during heap construction.However 15 | /// The codes 286 and 287 are needed to build a canonical tree (see MakeStaticTrees). 16 | /// 17 | private static readonly CodeData[] StaticLTree = new CodeData[LCODES + 2]; 18 | 19 | /// 20 | /// Gets the static distance tree. (Actually a trivial tree since all codes use 5 bits.) 21 | /// 22 | private static readonly CodeData[] StaticDTree = new CodeData[DCODES]; 23 | 24 | /// 25 | /// Initializes static members of the class. 26 | /// 27 | static Trees() => MakeStaticTrees(); 28 | 29 | /// 30 | /// Gets the static literal tree descriptor. 31 | /// 32 | public static StaticTreeDesc StaticLTreeDesc => new StaticTreeDesc(StaticLTree, ExtraLbits, LITERALS + 1, LCODES, MAXBITS); 33 | 34 | /// 35 | /// Gets the static distance tree descriptor. 36 | /// 37 | public static StaticTreeDesc StaticDTreeDesc => new StaticTreeDesc(StaticDTree, ExtraDbits, 0, DCODES, MAXBITS); 38 | 39 | /// 40 | /// Gets the static bit length tree descriptor. 41 | /// 42 | public static StaticTreeDesc StaticBlTreeDesc => new StaticTreeDesc(null, ExtraBlbits, 0, BLCODES, MAXBLBITS); 43 | 44 | private static void MakeStaticTrees() 45 | { 46 | fixed (CodeData* staticLTreePtr = StaticLTree) 47 | fixed (CodeData* staticDTreePtr = StaticDTree) 48 | { 49 | CodeData* static_ltree = staticLTreePtr; 50 | CodeData* static_dtree = staticDTreePtr; 51 | 52 | // The number of codes at each bit length for an optimal tree 53 | ushort* bl_count = stackalloc ushort[MAXBITS + 1]; 54 | 55 | // Construct the codes of the static literal tree. 56 | int n = 0; 57 | while (n <= 143) 58 | { 59 | static_ltree[n++].Len = 8; 60 | bl_count[8]++; 61 | } 62 | 63 | while (n <= 255) 64 | { 65 | static_ltree[n++].Len = 9; 66 | bl_count[9]++; 67 | } 68 | 69 | while (n <= 279) 70 | { 71 | static_ltree[n++].Len = 7; 72 | bl_count[7]++; 73 | } 74 | 75 | // Codes 286 and 287 do not exist, but we must include them in the tree construction 76 | // to get a canonical Huffman tree(longest code all ones) 77 | while (n <= 287) 78 | { 79 | static_ltree[n++].Len = 8; 80 | bl_count[8]++; 81 | } 82 | 83 | Gen_codes(static_ltree, LCODES + 1, bl_count); 84 | 85 | // The static distance tree is trivial. 86 | for (n = 0; n < DCODES; n++) 87 | { 88 | static_dtree[n].Len = 5; 89 | static_dtree[n].Code = (ushort)Bi_reverse((uint)n, 5); 90 | } 91 | } 92 | } 93 | 94 | /// 95 | /// A data structure describing a single value and its code string. 96 | /// A single struct with explicit offsets is used to represent different union properties. 97 | /// 98 | [StructLayout(LayoutKind.Explicit)] 99 | public struct CodeData 100 | { 101 | /// 102 | /// The frequency count. 103 | /// 104 | [FieldOffset(0)] 105 | public ushort Freq; 106 | 107 | /// 108 | /// The bit string. 109 | /// 110 | [FieldOffset(0)] 111 | public ushort Code; 112 | 113 | /// 114 | /// The father node in the Huffman tree. 115 | /// 116 | [FieldOffset(2)] 117 | public ushort Dad; 118 | 119 | /// 120 | /// The length of the bit string. 121 | /// 122 | [FieldOffset(2)] 123 | public ushort Len; 124 | } 125 | 126 | /// 127 | /// A static tree descriptor. 128 | /// 129 | public ref struct StaticTreeDesc 130 | { 131 | private readonly ReadOnlySpan staticTree; 132 | private readonly ReadOnlySpan extraBits; 133 | 134 | /// 135 | /// Initializes a new instance of the struct. 136 | /// 137 | /// static tree. 138 | /// extra bits. 139 | /// extra base. 140 | /// max elements. 141 | /// max length. 142 | public StaticTreeDesc( 143 | CodeData[] staticTree, 144 | ReadOnlySpan extraBits, 145 | int extraBase, 146 | int maxElements, 147 | int maxLength) 148 | { 149 | this.staticTree = staticTree; 150 | this.HasTree = staticTree != null; 151 | this.extraBits = extraBits; 152 | this.ExtraBase = extraBase; 153 | this.MaxElements = maxElements; 154 | this.MaxBitLength = maxLength; 155 | } 156 | 157 | /// 158 | /// Gets a value indicating whether the descriptor has a tree. 159 | /// 160 | public bool HasTree { get; } 161 | 162 | /// 163 | /// Gets the base index for extra_bits. 164 | /// 165 | public int ExtraBase { get; } 166 | 167 | /// 168 | /// Gets the max number of elements in the tree 169 | /// 170 | public int MaxElements { get; } 171 | 172 | /// 173 | /// Gets the max bit length for the codes 174 | /// 175 | public int MaxBitLength { get; } 176 | 177 | /// 178 | /// Returns a readonly reference to the span of code data at index 0. 179 | /// 180 | /// A reference to the at index 0. 181 | [MethodImpl(InliningOptions.ShortMethod)] 182 | public readonly ref CodeData GetCodeDataReference() 183 | => ref MemoryMarshal.GetReference(this.staticTree); 184 | 185 | /// 186 | /// Returns a readonly reference to the span of extra bit lengths at index 0. 187 | /// 188 | /// A reference to the at index 0. 189 | [MethodImpl(InliningOptions.ShortMethod)] 190 | public readonly ref byte GetExtraBitsReference() 191 | => ref MemoryMarshal.GetReference(this.extraBits); 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/ZlibStream/ZlibInputStream.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Buffers; 6 | using System.IO; 7 | using System.Runtime.CompilerServices; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace SixLabors.ZlibStream 11 | { 12 | /// 13 | /// Provides methods and properties used to compress and decompress 14 | /// input streams by using the zlib data format specification. 15 | /// 16 | public sealed class ZlibInputStream : Stream 17 | { 18 | private readonly int bufferSize; 19 | private readonly byte[] chunkBuffer; 20 | private readonly byte[] byteBuffer = new byte[1]; 21 | private readonly bool compress; 22 | private bool noMoreinput; 23 | private bool isFinished; 24 | private ZLibStream zStream; 25 | private bool isDisposed; 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | /// The output stream. 31 | public ZlibInputStream(Stream output) 32 | : this(output, new ZlibOptions()) 33 | { 34 | } 35 | 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | /// The output stream. 40 | /// The compression level. 41 | public ZlibInputStream(Stream output, CompressionLevel level) 42 | : this(output, new ZlibOptions() { CompressionLevel = level }) 43 | { 44 | } 45 | 46 | /// 47 | /// Initializes a new instance of the class. 48 | /// 49 | /// The input stream. 50 | /// The compression options. 51 | public ZlibInputStream(Stream input, ZlibOptions options) 52 | { 53 | this.Options = options; 54 | this.bufferSize = 8192; 55 | this.chunkBuffer = ArrayPool.Shared.Rent(this.bufferSize); 56 | this.zStream = new ZLibStream(options) 57 | { 58 | // TODO: Can these move? 59 | NextInIndex = 0, 60 | AvailableIn = 0 61 | }; 62 | 63 | this.compress = this.zStream.Compress; 64 | this.BaseStream = input; 65 | } 66 | 67 | /// 68 | /// Gets a reference to the underlying stream. 69 | /// 70 | public Stream BaseStream { get; } 71 | 72 | /// 73 | /// Gets or sets the options. 74 | /// 75 | public ZlibOptions Options { get; set; } 76 | 77 | /// 78 | /// Gets the total number of bytes input so far. 79 | /// 80 | public long TotalIn => this.zStream.TotalIn; 81 | 82 | /// 83 | /// Gets the total number of bytes output so far. 84 | /// 85 | public long TotalOut => this.zStream.TotalOut; 86 | 87 | /// 88 | public override bool CanRead => true; 89 | 90 | /// 91 | public override bool CanSeek => false; 92 | 93 | /// 94 | public override bool CanWrite => false; 95 | 96 | /// 97 | public override long Length => throw new NotSupportedException(); 98 | 99 | /// 100 | public override long Position 101 | { 102 | get => throw new NotSupportedException(); 103 | set => throw new NotSupportedException(); 104 | } 105 | 106 | /// 107 | [MethodImpl(InliningOptions.ShortMethod)] 108 | public override int ReadByte() 109 | => this.Read(this.byteBuffer, 0, 1) == -1 110 | ? -1 111 | : this.byteBuffer[0] & 0xFF; 112 | 113 | #if SUPPORTS_SPAN_STREAM 114 | 115 | /// 116 | public override int Read(Span buffer) => this.ReadCore(buffer); 117 | 118 | #endif 119 | 120 | /// 121 | [MethodImpl(InliningOptions.ShortMethod)] 122 | public override int Read(byte[] buffer, int offset, int count) 123 | { 124 | if (buffer is null) 125 | { 126 | ThrowHelper.ThrowArgumentNullException(nameof(buffer)); 127 | } 128 | 129 | return this.ReadCore(buffer.AsSpan(offset, count)); 130 | } 131 | 132 | [MethodImpl(InliningOptions.ShortMethod)] 133 | private unsafe int ReadCore(Span buffer) 134 | { 135 | if (buffer.IsEmpty) 136 | { 137 | return 0; 138 | } 139 | 140 | fixed (byte* nextIn = &MemoryMarshal.GetReference(this.chunkBuffer.AsSpan())) 141 | fixed (byte* nextOut = &MemoryMarshal.GetReference(buffer)) 142 | { 143 | CompressionState state; 144 | this.zStream.NextIn = nextIn; 145 | this.zStream.NextOut = nextOut; 146 | this.zStream.NextOutIndex = 0; 147 | this.zStream.AvailableOut = buffer.Length; 148 | do 149 | { 150 | if ((this.zStream.AvailableIn == 0) && (!this.noMoreinput)) 151 | { 152 | // If buffer is empty and more input is available, refill it 153 | this.zStream.NextInIndex = 0; 154 | this.zStream.AvailableIn = this.BaseStream.Read(this.chunkBuffer, 0, this.bufferSize); 155 | 156 | if (this.zStream.AvailableIn == -1) 157 | { 158 | this.zStream.AvailableIn = 0; 159 | this.noMoreinput = true; 160 | } 161 | } 162 | 163 | state = this.compress 164 | ? this.zStream.Deflate(this.Options.FlushMode) 165 | : this.zStream.Inflate(this.Options.FlushMode); 166 | 167 | if (this.noMoreinput && (state == CompressionState.ZBUFERROR)) 168 | { 169 | return -1; 170 | } 171 | 172 | if (state != CompressionState.ZOK && state != CompressionState.ZSTREAMEND) 173 | { 174 | ThrowHelper.ThrowCompressionException(this.compress, this.zStream.Message); 175 | } 176 | 177 | if (this.noMoreinput && (this.zStream.AvailableOut == buffer.Length)) 178 | { 179 | return -1; 180 | } 181 | } 182 | while (this.zStream.AvailableOut > 0 && state == CompressionState.ZOK); 183 | 184 | return buffer.Length - this.zStream.AvailableOut; 185 | } 186 | } 187 | 188 | /// 189 | public override void Flush() => this.BaseStream.Flush(); 190 | 191 | /// 192 | public override long Seek(long offset, SeekOrigin origin) 193 | => throw new NotSupportedException(); 194 | 195 | /// 196 | public override void SetLength(long value) 197 | => throw new NotSupportedException(); 198 | 199 | /// 200 | public override void Write(byte[] buffer, int offset, int count) 201 | => throw new NotSupportedException(); 202 | 203 | /// 204 | protected override void Dispose(bool disposing) 205 | { 206 | if (!this.isDisposed) 207 | { 208 | if (disposing) 209 | { 210 | try 211 | { 212 | this.Finish(); 213 | } 214 | finally 215 | { 216 | this.zStream.Dispose(); 217 | this.zStream = null; 218 | ArrayPool.Shared.Return(this.chunkBuffer); 219 | } 220 | } 221 | 222 | this.isDisposed = true; 223 | base.Dispose(disposing); 224 | } 225 | } 226 | 227 | /// 228 | /// Finishes the stream. 229 | /// 230 | private unsafe void Finish() 231 | { 232 | if (!this.isFinished) 233 | { 234 | fixed (byte* nextOut = &MemoryMarshal.GetReference(this.chunkBuffer.AsSpan())) 235 | { 236 | CompressionState state; 237 | do 238 | { 239 | this.zStream.NextOut = nextOut; 240 | this.zStream.NextOutIndex = 0; 241 | this.zStream.AvailableOut = this.bufferSize; 242 | state = this.compress 243 | ? this.zStream.Deflate(FlushMode.Finish) 244 | : this.zStream.Inflate(FlushMode.Finish); 245 | 246 | if (state != CompressionState.ZSTREAMEND && state != CompressionState.ZOK) 247 | { 248 | ThrowHelper.ThrowCompressionException(this.compress, this.zStream.Message); 249 | } 250 | 251 | if (this.bufferSize - this.zStream.AvailableOut > 0) 252 | { 253 | this.BaseStream.Write(this.chunkBuffer, 0, this.bufferSize - this.zStream.AvailableOut); 254 | } 255 | 256 | if (state == CompressionState.ZSTREAMEND) 257 | { 258 | break; 259 | } 260 | } 261 | while (this.zStream.AvailableIn > 0 || this.zStream.AvailableOut == 0); 262 | 263 | this.isFinished = true; 264 | } 265 | } 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/ZlibStream/ZlibOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace SixLabors.ZlibStream 5 | { 6 | /// 7 | /// Provides options for ZLib compression and decompression operations. 8 | /// 9 | public sealed class ZlibOptions 10 | { 11 | /// 12 | /// Gets or sets the compression level. 13 | /// 14 | public CompressionLevel? CompressionLevel { get; set; } 15 | 16 | /// 17 | /// Gets or sets the compression strategy. 18 | /// 19 | public CompressionStrategy CompressionStrategy { get; set; } 20 | 21 | /// 22 | /// Gets or sets the flush mode. 23 | /// 24 | public FlushMode FlushMode { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ZlibStream/ZlibOutputStream.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Buffers; 6 | using System.IO; 7 | using System.Runtime.CompilerServices; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace SixLabors.ZlibStream 11 | { 12 | /// 13 | /// Provides methods and properties used to compress and decompress 14 | /// output streams by using the zlib data format specification. 15 | /// 16 | public sealed class ZlibOutputStream : Stream 17 | { 18 | private readonly int bufferSize; 19 | private readonly byte[] chunkBuffer; 20 | private readonly byte[] byteBuffer = new byte[1]; 21 | private readonly bool compress; 22 | private bool isFinished; 23 | private ZLibStream zStream; 24 | private bool isDisposed; 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// The output stream. 30 | public ZlibOutputStream(Stream output) 31 | : this(output, new ZlibOptions()) 32 | { 33 | } 34 | 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// 38 | /// The output stream. 39 | /// The compression level. 40 | public ZlibOutputStream(Stream output, CompressionLevel level) 41 | : this(output, new ZlibOptions() { CompressionLevel = level }) 42 | { 43 | } 44 | 45 | /// 46 | /// Initializes a new instance of the class. 47 | /// 48 | /// The output stream. 49 | /// The compression options. 50 | public ZlibOutputStream(Stream output, ZlibOptions options) 51 | { 52 | this.Options = options; 53 | this.bufferSize = 512; 54 | this.chunkBuffer = ArrayPool.Shared.Rent(this.bufferSize); 55 | this.zStream = new ZLibStream(options); 56 | this.compress = this.zStream.Compress; 57 | this.BaseStream = output; 58 | } 59 | 60 | /// 61 | /// Gets a reference to the underlying stream. 62 | /// 63 | public Stream BaseStream { get; } 64 | 65 | /// 66 | /// Gets or sets the options. 67 | /// 68 | public ZlibOptions Options { get; set; } 69 | 70 | /// 71 | /// Gets the total number of bytes input so far. 72 | /// 73 | public long TotalIn => this.zStream.TotalIn; 74 | 75 | /// 76 | /// Gets the total number of bytes output so far. 77 | /// 78 | public long TotalOut => this.zStream.TotalOut; 79 | 80 | /// 81 | public override bool CanRead => false; 82 | 83 | /// 84 | public override bool CanSeek => false; 85 | 86 | /// 87 | public override bool CanWrite => true; 88 | 89 | /// 90 | public override long Length => throw new NotSupportedException(); 91 | 92 | /// 93 | public override long Position 94 | { 95 | get => throw new NotSupportedException(); 96 | set => throw new NotSupportedException(); 97 | } 98 | 99 | /// 100 | [MethodImpl(InliningOptions.ShortMethod)] 101 | public override void WriteByte(byte value) 102 | { 103 | this.byteBuffer[0] = value; 104 | this.Write(this.byteBuffer, 0, 1); 105 | } 106 | 107 | #if SUPPORTS_SPAN_STREAM 108 | /// 109 | public override void Write(ReadOnlySpan buffer) => this.WriteCore(buffer); 110 | #endif 111 | 112 | /// 113 | [MethodImpl(InliningOptions.ShortMethod)] 114 | public override void Write(byte[] buffer, int offset, int count) 115 | { 116 | if (buffer is null) 117 | { 118 | ThrowHelper.ThrowArgumentNullException(nameof(buffer)); 119 | } 120 | 121 | this.WriteCore(buffer.AsSpan(offset, count)); 122 | } 123 | 124 | [MethodImpl(InliningOptions.ShortMethod)] 125 | private unsafe void WriteCore(ReadOnlySpan buffer) 126 | { 127 | if (buffer.IsEmpty) 128 | { 129 | return; 130 | } 131 | 132 | fixed (byte* nextIn = &MemoryMarshal.GetReference(buffer)) 133 | { 134 | fixed (byte* nextOut = &MemoryMarshal.GetReference(this.chunkBuffer.AsSpan())) 135 | { 136 | CompressionState state; 137 | this.zStream.NextIn = nextIn; 138 | this.zStream.NextInIndex = 0; 139 | this.zStream.AvailableIn = buffer.Length; 140 | do 141 | { 142 | this.zStream.NextOut = nextOut; 143 | this.zStream.NextOutIndex = 0; 144 | this.zStream.AvailableOut = this.bufferSize; 145 | state = this.compress 146 | ? this.zStream.Deflate(this.Options.FlushMode) 147 | : this.zStream.Inflate(this.Options.FlushMode); 148 | 149 | if (state != CompressionState.ZOK && state != CompressionState.ZSTREAMEND) 150 | { 151 | ThrowHelper.ThrowCompressionException(this.compress, this.zStream.Message); 152 | } 153 | 154 | this.BaseStream.Write(this.chunkBuffer, 0, this.bufferSize - this.zStream.AvailableOut); 155 | if (!this.compress && this.zStream.AvailableIn == 0 && this.zStream.AvailableOut == 0) 156 | { 157 | break; 158 | } 159 | 160 | if (state == CompressionState.ZSTREAMEND) 161 | { 162 | break; 163 | } 164 | } 165 | while (this.zStream.AvailableIn > 0 || this.zStream.AvailableOut == 0); 166 | } 167 | } 168 | } 169 | 170 | /// 171 | public override void Flush() => this.BaseStream.Flush(); 172 | 173 | /// 174 | public override int Read(byte[] buffer, int offset, int count) 175 | => throw new NotSupportedException(); 176 | 177 | /// 178 | public override long Seek(long offset, SeekOrigin origin) 179 | => throw new NotSupportedException(); 180 | 181 | /// 182 | public override void SetLength(long value) 183 | => throw new NotSupportedException(); 184 | 185 | /// 186 | protected override void Dispose(bool disposing) 187 | { 188 | if (!this.isDisposed) 189 | { 190 | if (disposing) 191 | { 192 | try 193 | { 194 | this.Finish(); 195 | } 196 | finally 197 | { 198 | this.zStream.Dispose(); 199 | this.zStream = null; 200 | ArrayPool.Shared.Return(this.chunkBuffer); 201 | } 202 | } 203 | 204 | this.isDisposed = true; 205 | base.Dispose(disposing); 206 | } 207 | } 208 | 209 | /// 210 | /// Finishes the stream. 211 | /// 212 | [MethodImpl(InliningOptions.ShortMethod)] 213 | private unsafe void Finish() 214 | { 215 | if (!this.isFinished) 216 | { 217 | fixed (byte* nextOut = &MemoryMarshal.GetReference(this.chunkBuffer.AsSpan())) 218 | { 219 | CompressionState state; 220 | do 221 | { 222 | this.zStream.NextOut = nextOut; 223 | this.zStream.NextOutIndex = 0; 224 | this.zStream.AvailableOut = this.bufferSize; 225 | state = this.compress 226 | ? this.zStream.Deflate(FlushMode.Finish) 227 | : this.zStream.Inflate(FlushMode.Finish); 228 | 229 | if (state != CompressionState.ZSTREAMEND && state != CompressionState.ZOK) 230 | { 231 | ThrowHelper.ThrowCompressionException(this.compress, this.zStream.Message); 232 | } 233 | 234 | if (this.bufferSize - this.zStream.AvailableOut > 0) 235 | { 236 | this.BaseStream.Write(this.chunkBuffer, 0, this.bufferSize - this.zStream.AvailableOut); 237 | } 238 | 239 | if (state == CompressionState.ZSTREAMEND) 240 | { 241 | break; 242 | } 243 | } 244 | while (this.zStream.AvailableIn > 0 || this.zStream.AvailableOut == 0); 245 | } 246 | 247 | try 248 | { 249 | this.Flush(); 250 | } 251 | finally 252 | { 253 | this.isFinished = true; 254 | } 255 | } 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/ZlibStream/ZlibStream.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace SixLabors.ZlibStream 8 | { 9 | /// 10 | /// The zlib stream class. 11 | /// 12 | internal sealed unsafe class ZLibStream : IDisposable 13 | { 14 | private const int MAXWBITS = 15; // 32K LZ77 window 15 | private const int DEFWBITS = MAXWBITS; 16 | private bool isDisposed; 17 | 18 | public ZLibStream(ZlibOptions options) 19 | { 20 | if (options.CompressionLevel is null) 21 | { 22 | this.InflateInit(); 23 | } 24 | else 25 | { 26 | this.Compress = true; 27 | this.DeflateInit(options); 28 | } 29 | } 30 | 31 | /// 32 | /// Gets or sets the next input bytes. 33 | /// 34 | public byte* NextIn { get; set; } 35 | 36 | /// 37 | /// Gets or sets the next output bytes. 38 | /// 39 | public byte* NextOut { get; set; } 40 | 41 | /// 42 | /// Gets or sets the next input byte index. 43 | /// 44 | public int NextInIndex { get; set; } 45 | 46 | /// 47 | /// Gets or sets the number of bytes available at . 48 | /// 49 | public int AvailableIn { get; set; } 50 | 51 | /// 52 | /// Gets or sets the total number of input bytes read so far. 53 | /// 54 | public long TotalIn { get; set; } 55 | 56 | /// 57 | /// Gets or sets the next output byte index. 58 | /// 59 | public int NextOutIndex { get; set; } 60 | 61 | /// 62 | /// Gets or sets the remaining free space at . 63 | /// 64 | public int AvailableOut { get; set; } 65 | 66 | /// 67 | /// Gets or sets the total number of bytes output so far. 68 | /// 69 | public long TotalOut { get; set; } 70 | 71 | /// 72 | /// Gets or sets the stream's error message. 73 | /// 74 | public string Message { get; set; } 75 | 76 | /// 77 | /// Gets or sets the stream data checksum. 78 | /// 79 | public uint Adler { get; set; } 80 | 81 | /// 82 | /// Gets the current deflate instance for this class. 83 | /// 84 | public Deflate DeflateState { get; private set; } 85 | 86 | /// 87 | /// Gets the current inflate instance for this class. 88 | /// 89 | public Inflate InflateState { get; private set; } 90 | 91 | /// 92 | /// Gets or sets the data type to this instance of this class. 93 | /// 94 | public int DataType { get; set; } // best guess about the data type: ascii or binary 95 | 96 | /// 97 | /// Gets a value indicating whether compression is taking place. 98 | /// 99 | public bool Compress { get; } 100 | 101 | /// 102 | /// Initializes decompression. 103 | /// 104 | public void InflateInit() 105 | => this.InflateInit(DEFWBITS); 106 | 107 | /// 108 | /// Initializes decompression. 109 | /// 110 | /// The window size in bits. 111 | public void InflateInit(int windowBits) 112 | => this.InflateState = new Inflate(this, windowBits); 113 | 114 | /// 115 | /// Decompresses data. 116 | /// 117 | /// The flush mode to use. 118 | /// The zlib status state. 119 | public CompressionState Inflate(FlushMode strategy) 120 | => this.InflateState == null 121 | ? CompressionState.ZSTREAMERROR 122 | : ZlibStream.Inflate.Decompress(this, strategy); 123 | 124 | /// 125 | /// Syncs inflate. 126 | /// 127 | /// The zlib status state. 128 | public CompressionState InflateSync() 129 | => this.InflateState == null 130 | ? CompressionState.ZSTREAMERROR 131 | : ZlibStream.Inflate.InflateSync(this); 132 | 133 | /// 134 | /// Sets the inflate dictionary. 135 | /// 136 | /// The dictionary to use. 137 | /// The dictionary length. 138 | /// The zlib status state. 139 | public CompressionState InflateSetDictionary(byte[] dictionary, int dictLength) 140 | => this.InflateState == null 141 | ? CompressionState.ZSTREAMERROR 142 | : ZlibStream.Inflate.InflateSetDictionary(this, dictionary, dictLength); 143 | 144 | /// 145 | /// Initializes compression. 146 | /// 147 | /// The options. 148 | public void DeflateInit(ZlibOptions options) 149 | => this.DeflateInit(options, MAXWBITS); 150 | 151 | /// 152 | /// Initializes compression. 153 | /// 154 | /// The options. 155 | /// The window bits to use. 156 | public void DeflateInit(ZlibOptions options, int windowBits) 157 | => this.DeflateState = new Deflate(this, options, windowBits); 158 | 159 | /// 160 | /// Compress data. 161 | /// 162 | /// The flush mode to use on the data. 163 | /// The zlib status state. 164 | public CompressionState Deflate(FlushMode flush) 165 | => this.DeflateState == null 166 | ? CompressionState.ZSTREAMERROR 167 | : this.DeflateState.Compress(this, flush); 168 | 169 | /// 170 | /// Sets the compression parameters. 171 | /// 172 | /// The compression level to use. 173 | /// The strategy to use for compression. 174 | /// The zlib status state. 175 | public CompressionState DeflateParams(CompressionLevel level, CompressionStrategy strategy) 176 | => this.DeflateState == null 177 | ? CompressionState.ZSTREAMERROR 178 | : this.DeflateState.DeflateParams(this, level, strategy); 179 | 180 | /// 181 | /// Sets the deflate dictionary. 182 | /// 183 | /// The dictionary to use. 184 | /// The dictionary length. 185 | /// The zlib status state. 186 | public CompressionState DeflateSetDictionary(byte[] dictionary, int dictLength) 187 | => this.DeflateState == null 188 | ? CompressionState.ZSTREAMERROR 189 | : this.DeflateState.DeflateSetDictionary(this, dictionary, dictLength); 190 | 191 | // Read a new buffer from the current input stream, update the adler32 192 | // and total number of bytes read. All deflate() input goes through 193 | // this function so some applications may wish to modify it to avoid 194 | // allocating a large strm->next_in buffer and copying from it. 195 | // (See also flush_pending()). 196 | [MethodImpl(InliningOptions.ShortMethod)] 197 | public int ReadBuffer(byte* buffer, int size) 198 | { 199 | int len = this.AvailableIn; 200 | 201 | if (len > size) 202 | { 203 | len = size; 204 | } 205 | 206 | if (len == 0) 207 | { 208 | return 0; 209 | } 210 | 211 | this.AvailableIn -= len; 212 | 213 | if (this.DeflateState.NoHeader == 0) 214 | { 215 | this.Adler = Adler32.Calculate(this.Adler, new Span(this.NextIn + this.NextInIndex, len)); 216 | } 217 | 218 | Unsafe.CopyBlockUnaligned(buffer, this.NextIn + this.NextInIndex, (uint)len); 219 | this.NextInIndex += len; 220 | this.TotalIn += len; 221 | return len; 222 | } 223 | 224 | /// 225 | public void Dispose() => this.Dispose(true); 226 | 227 | private void Dispose(bool disposing) 228 | { 229 | if (!this.isDisposed) 230 | { 231 | if (disposing) 232 | { 233 | if (this.Compress) 234 | { 235 | this.DeflateState?.Dispose(); 236 | } 237 | else 238 | { 239 | this.InflateState?.Dispose(); 240 | } 241 | } 242 | 243 | this.isDisposed = true; 244 | } 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/ZlibStream/ZlibStream.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SixLabors.ZlibStream 5 | SixLabors.ZlibStream 6 | SixLabors.ZlibStream 7 | SixLabors.ZlibStream 8 | sixlabors.128.png 9 | Apache-2.0 10 | https://github.com/SixLabors/ZlibStream/ 11 | $(RepositoryUrl) 12 | A cross platform, managed, implementation of Zlib. 13 | en 14 | 1.0 15 | 16 | 17 | 18 | 19 | 20 | net6.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 21 | 22 | 23 | 24 | 25 | netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/ZlibStream/ZlibStreamException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.IO; 6 | #if SUPPORTS_SERIALIZATION 7 | using System.Runtime.Serialization; 8 | #endif 9 | 10 | namespace SixLabors.ZlibStream 11 | { 12 | /// 13 | /// The exception that is thrown when an Zlib error occurs. 14 | /// 15 | [Serializable] 16 | public sealed class ZlibStreamException : IOException 17 | { 18 | /// 19 | /// Initializes a new instance of the class with no argrument. 20 | /// 21 | public ZlibStreamException() 22 | { 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class with 27 | /// its message string set to , its HRESULT set to COR_E_IO, 28 | /// and its inner exception set to . 29 | /// A that describes the error. The content of is intended to be understood by humans. The caller of this constructor is required to ensure that this string has been localized for the current system culture. 30 | public ZlibStreamException(string message) 31 | : base(message) 32 | { 33 | } 34 | 35 | /// 36 | /// Initializes a new instance of the class with 37 | /// a specified error message and a reference to the inner exception that is the cause 38 | /// of this exception. 39 | /// The error message that explains the reason for the exception. 40 | /// 41 | /// The exception that is the cause of the current exception. 42 | /// If the parameter is not , the 43 | /// current exception is raised in a block that handles the inner 44 | /// exception. 45 | /// 46 | public ZlibStreamException(string message, Exception innerException) 47 | : base(message, innerException) 48 | { 49 | } 50 | 51 | /// 52 | /// Initializes a new instance of the class with its message 53 | /// string set to and its HRESULT user-defined. 54 | /// 55 | /// 56 | /// A that describes the error. 57 | /// The content of is intended to be understood by humans. 58 | /// The caller of this constructor is required to ensure that this string has been localized 59 | /// for the current system culture. 60 | /// 61 | /// An integer identifying the error that has occurred. 62 | public ZlibStreamException(string message, int hresult) 63 | : base(message, hresult) 64 | { 65 | } 66 | 67 | #if SUPPORTS_SERIALIZATION 68 | /// 69 | /// Initializes a new instance of the class 70 | /// with the specified serialization and context information. 71 | /// 72 | /// The data for serializing or deserializing the object. 73 | /// The source and destination for the object. 74 | private ZlibStreamException(SerializationInfo info, StreamingContext context) 75 | : base(info, context) 76 | { 77 | } 78 | #endif 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "orderingRules": { 5 | "usingDirectivesPlacement": "outsideNamespace", 6 | "elementOrder": [ 7 | "kind" 8 | ] 9 | }, 10 | "documentationRules": { 11 | "xmlHeader": false, 12 | "documentInternalElements": false, 13 | "copyrightText": "Copyright (c) Six Labors and contributors.\nSee LICENSE for more details." 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/ZlibStream.Benchmarks/Adler32Benchmark.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using BenchmarkDotNet.Attributes; 6 | using SixLabors.ZlibStream; 7 | 8 | namespace ZlibStream.Benchmarks 9 | { 10 | [Config(typeof(ShortRun))] 11 | public class Adler32Benchmark 12 | { 13 | private byte[] data; 14 | 15 | [Params(1024, 2048, 4096)] 16 | public int Count { get; set; } 17 | 18 | [GlobalSetup] 19 | public void SetUp() 20 | { 21 | this.data = new byte[this.Count]; 22 | new Random(1).NextBytes(this.data); 23 | } 24 | 25 | [Benchmark(Baseline = true)] 26 | public long SharpZipLibUpdate() 27 | { 28 | var adler32 = new ICSharpCode.SharpZipLib.Checksum.Adler32(); 29 | adler32.Update(this.data); 30 | return adler32.Value; 31 | } 32 | 33 | [Benchmark] 34 | public long SixLaborsUpdate() 35 | { 36 | return Adler32.Calculate(1, this.data); 37 | } 38 | 39 | [Benchmark] 40 | public long ZlibManagedUpdate() 41 | { 42 | return ZlibManagedAdler32.Calculate(1, this.data, 0, this.data.Length); 43 | } 44 | } 45 | 46 | // Reference implementation taken from zlib.managed. 47 | internal static class ZlibManagedAdler32 48 | { 49 | // largest prime smaller than 65536 50 | private const int BASE = 65521; 51 | 52 | // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 53 | private const int NMAX = 5552; 54 | 55 | internal static long Calculate(long adler, byte[] buf, int index, int len) 56 | { 57 | if (buf == null) 58 | { 59 | return 1L; 60 | } 61 | 62 | var s1 = adler & 0xFFFF; 63 | var s2 = (adler >> 16) & 0xFFFF; 64 | int k; 65 | 66 | while (len > 0) 67 | { 68 | k = len < NMAX ? len : NMAX; 69 | len -= k; 70 | while (k >= 16) 71 | { 72 | s1 += buf[index++] & 0xFF; 73 | s2 += s1; 74 | s1 += buf[index++] & 0xFF; 75 | s2 += s1; 76 | s1 += buf[index++] & 0xFF; 77 | s2 += s1; 78 | s1 += buf[index++] & 0xFF; 79 | s2 += s1; 80 | s1 += buf[index++] & 0xFF; 81 | s2 += s1; 82 | s1 += buf[index++] & 0xFF; 83 | s2 += s1; 84 | s1 += buf[index++] & 0xFF; 85 | s2 += s1; 86 | s1 += buf[index++] & 0xFF; 87 | s2 += s1; 88 | s1 += buf[index++] & 0xFF; 89 | s2 += s1; 90 | s1 += buf[index++] & 0xFF; 91 | s2 += s1; 92 | s1 += buf[index++] & 0xFF; 93 | s2 += s1; 94 | s1 += buf[index++] & 0xFF; 95 | s2 += s1; 96 | s1 += buf[index++] & 0xFF; 97 | s2 += s1; 98 | s1 += buf[index++] & 0xFF; 99 | s2 += s1; 100 | s1 += buf[index++] & 0xFF; 101 | s2 += s1; 102 | s1 += buf[index++] & 0xFF; 103 | s2 += s1; 104 | k -= 16; 105 | } 106 | 107 | if (k != 0) 108 | { 109 | do 110 | { 111 | s1 += buf[index++] & 0xFF; 112 | s2 += s1; 113 | } 114 | while (--k != 0); 115 | } 116 | 117 | s1 %= BASE; 118 | s2 %= BASE; 119 | } 120 | 121 | return (s2 << 16) | s1; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /tests/ZlibStream.Benchmarks/BinaryPrimitiveBenchmarks.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Buffers.Binary; 5 | using BenchmarkDotNet.Attributes; 6 | 7 | namespace ZlibStream.Benchmarks 8 | { 9 | public class BinaryPrimitiveBenchmarks 10 | { 11 | private byte[] buffer = new byte[2]; 12 | 13 | [Benchmark] 14 | public void PutShort() 15 | { 16 | BinaryPrimitives.WriteInt16LittleEndian(this.buffer, 255); 17 | } 18 | 19 | [Benchmark] 20 | public void PutShort2() 21 | { 22 | const int w = 255; 23 | this.buffer[0] = (byte)w; 24 | this.buffer[1] = (byte)w >> 8; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/ZlibStream.Benchmarks/Config.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | #if OS_WINDOWS 5 | using System.Security.Principal; 6 | using BenchmarkDotNet.Diagnostics.Windows; 7 | #endif 8 | 9 | using System; 10 | using System.Linq; 11 | using System.Reflection; 12 | using BenchmarkDotNet.Columns; 13 | using BenchmarkDotNet.Configs; 14 | using BenchmarkDotNet.Diagnosers; 15 | using BenchmarkDotNet.Environments; 16 | using BenchmarkDotNet.Jobs; 17 | using BenchmarkDotNet.Reports; 18 | using BenchmarkDotNet.Running; 19 | 20 | namespace ZlibStream.Benchmarks 21 | { 22 | public class Config : ManualConfig 23 | { 24 | public Config() 25 | { 26 | this.AddDiagnoser(MemoryDiagnoser.Default); 27 | 28 | #if OS_WINDOWS 29 | if (this.IsElevated) 30 | { 31 | this.AddDiagnoser(new NativeMemoryProfiler()); 32 | } 33 | #endif 34 | } 35 | 36 | #if OS_WINDOWS 37 | private bool IsElevated => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); 38 | #endif 39 | 40 | } 41 | 42 | public class DeflateConfig : ShortRun 43 | { 44 | public DeflateConfig() 45 | => this.AddColumn(new ByteSizeColumn(nameof(DeflateCorpusBenchmark.Compression))); 46 | } 47 | 48 | public class ShortRun : Config 49 | { 50 | public ShortRun() 51 | => this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); 52 | } 53 | 54 | public class ByteSizeColumn : IColumn 55 | { 56 | private readonly string parameterName; 57 | 58 | public ByteSizeColumn(string parameterName) => this.parameterName = parameterName; 59 | 60 | public string Id => nameof(ByteSizeColumn) + "." + this.ColumnName + "." + this.parameterName; 61 | 62 | public string ColumnName => "Bytes"; 63 | 64 | public bool AlwaysShow => true; 65 | 66 | public ColumnCategory Category => ColumnCategory.Custom; 67 | 68 | public int PriorityInCategory => 0; 69 | 70 | public bool IsNumeric => true; 71 | 72 | public UnitType UnitType => UnitType.Dimensionless; 73 | 74 | public string Legend => $"Output length in {this.ColumnName.ToLowerInvariant()}."; 75 | 76 | public string GetValue(Summary summary, BenchmarkCase benchmarkCase) 77 | { 78 | Descriptor descriptor = benchmarkCase.Descriptor; 79 | 80 | var instance = Activator.CreateInstance(descriptor.Type); 81 | descriptor.GlobalSetupMethod?.Invoke(instance, Array.Empty()); 82 | 83 | var p = benchmarkCase.Parameters.Items.First(x => x.Name == this.parameterName).Value; 84 | if (p is int pint) 85 | { 86 | PropertyInfo prop = descriptor.Type.GetProperty(this.parameterName); 87 | prop.SetValue(instance, pint); 88 | } 89 | 90 | var args = Array.Empty(); 91 | if (benchmarkCase.HasArguments) 92 | { 93 | args = benchmarkCase.Parameters.Items.Where(x => x.IsArgument).Select(x => x.Value).ToArray(); 94 | } 95 | 96 | return descriptor.WorkloadMethod.Invoke(instance, args).ToString(); 97 | } 98 | 99 | public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) 100 | => this.GetValue(summary, benchmarkCase); 101 | 102 | public bool IsAvailable(Summary summary) => true; 103 | 104 | public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) 105 | => false; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/ZlibStream.Benchmarks/DeflateCorpusBenchmark.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using BenchmarkDotNet.Attributes; 7 | using Elskom.Generic.Libs; 8 | using ICSharpCode.SharpZipLib.Zip.Compression; 9 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; 10 | using SixLabors.ZlibStream; 11 | using ZlibStream.Tests.TestUtilities; 12 | 13 | namespace ZlibStream.Benchmarks 14 | { 15 | [Config(typeof(DeflateConfig))] 16 | public class DeflateCorpusBenchmark 17 | { 18 | private Dictionary data = new Dictionary(); 19 | 20 | public IEnumerable Files { get; } = new[] 21 | { 22 | Corpus.Alice, 23 | Corpus.CCITT, 24 | Corpus.CompressionPointers, 25 | Corpus.Electronic, 26 | Corpus.Excel, 27 | Corpus.Fields, 28 | Corpus.GnuManual, 29 | Corpus.Grammar, 30 | Corpus.ParadiseLost, 31 | Corpus.Shakespeare, 32 | Corpus.Sum 33 | }; 34 | 35 | [GlobalSetup] 36 | public void SetUp() 37 | { 38 | foreach (var file in this.Files) 39 | { 40 | using (FileStream fs = File.OpenRead(Path.Combine(TestEnvironment.CorpusDirectoryFullPath, file))) 41 | using (var ms = new MemoryStream()) 42 | { 43 | fs.CopyTo(ms); 44 | this.data.Add(file, ms.ToArray()); 45 | } 46 | } 47 | } 48 | 49 | [Params(1, 3, 6)] 50 | public int Compression { get; set; } 51 | 52 | [Benchmark(Baseline = true, Description = "Microsoft")] 53 | [ArgumentsSource(nameof(Files))] 54 | public long DotNetDeflate(string file) 55 | { 56 | using (var output = new MemoryStream()) 57 | { 58 | using (var deflate = new DotNetZlibDeflateStream(output, this.Compression)) 59 | { 60 | var buffer = this.data[file]; 61 | deflate.Write(buffer, 0, buffer.Length); 62 | } 63 | 64 | return output.Length; 65 | } 66 | } 67 | 68 | [Benchmark(Description = "SharpZipLib")] 69 | [ArgumentsSource(nameof(Files))] 70 | public long SharpZipLibDeflate(string file) 71 | { 72 | using (var output = new MemoryStream()) 73 | { 74 | var deflater = new Deflater(this.Compression); 75 | using (var deflate = new DeflaterOutputStream(output, deflater)) 76 | { 77 | deflate.IsStreamOwner = false; 78 | var buffer = this.data[file]; 79 | deflate.Write(buffer, 0, buffer.Length); 80 | } 81 | 82 | return output.Length; 83 | } 84 | } 85 | 86 | [Benchmark(Description = "SixLabors")] 87 | [ArgumentsSource(nameof(Files))] 88 | public long SixLaborsDeflate(string file) 89 | { 90 | using (var output = new MemoryStream()) 91 | { 92 | using (var deflate = new ZlibOutputStream(output, (CompressionLevel)this.Compression)) 93 | { 94 | var buffer = this.data[file]; 95 | deflate.Write(buffer, 0, buffer.Length); 96 | } 97 | 98 | return output.Length; 99 | } 100 | } 101 | 102 | [Benchmark(Description = "ZLibManaged")] 103 | [ArgumentsSource(nameof(Files))] 104 | public long ZlibManagedDeflate(string file) 105 | { 106 | using (var output = new MemoryStream()) 107 | { 108 | using (var deflate = new ZOutputStream(output, (ZlibCompression)this.Compression)) 109 | { 110 | var buffer = this.data[file]; 111 | deflate.Write(buffer, 0, buffer.Length); 112 | } 113 | 114 | return output.Length; 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/ZlibStream.Benchmarks/DeflateSparseBenchmark.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.IO; 5 | using BenchmarkDotNet.Attributes; 6 | using Elskom.Generic.Libs; 7 | using ICSharpCode.SharpZipLib.Zip.Compression; 8 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; 9 | using SixLabors.ZlibStream; 10 | 11 | namespace ZlibStream.Benchmarks 12 | { 13 | [Config(typeof(DeflateConfig))] 14 | public class DeflateSparseBenchmark 15 | { 16 | private static byte[] data = GetImageBytes(3500, 3500); 17 | 18 | [Params(1, 3, 6)] 19 | public int Compression { get; set; } 20 | 21 | [Benchmark(Baseline = true, Description = "Microsoft")] 22 | public long DotNetDeflate() 23 | { 24 | using (var output = new MemoryStream()) 25 | { 26 | using (var deflate = new DotNetZlibDeflateStream(output, this.Compression)) 27 | { 28 | var buffer = data; 29 | deflate.Write(buffer, 0, buffer.Length); 30 | } 31 | 32 | return output.Length; 33 | } 34 | } 35 | 36 | [Benchmark(Description = "SharpZipLib")] 37 | public long SharpZipLibDeflate() 38 | { 39 | using (var output = new MemoryStream()) 40 | { 41 | var deflater = new Deflater(this.Compression); 42 | using (var deflate = new DeflaterOutputStream(output, deflater)) 43 | { 44 | deflate.IsStreamOwner = false; 45 | var buffer = data; 46 | deflate.Write(buffer, 0, buffer.Length); 47 | } 48 | 49 | return output.Length; 50 | } 51 | } 52 | 53 | [Benchmark(Description = "SixLabors")] 54 | public long SixLaborsDeflate() 55 | { 56 | using (var output = new MemoryStream()) 57 | { 58 | using (var deflate = new ZlibOutputStream(output, (CompressionLevel)this.Compression)) 59 | { 60 | var buffer = data; 61 | deflate.Write(buffer, 0, buffer.Length); 62 | } 63 | 64 | return output.Length; 65 | } 66 | } 67 | 68 | [Benchmark(Description = "ZLibManaged")] 69 | public long ZlibManagedDeflate() 70 | { 71 | using (var output = new MemoryStream()) 72 | { 73 | using (var deflate = new ZOutputStream(output, (ZlibCompression)this.Compression)) 74 | { 75 | var buffer = data; 76 | deflate.Write(buffer, 0, buffer.Length); 77 | } 78 | 79 | return output.Length; 80 | } 81 | } 82 | 83 | private static byte[] GetImageBytes(int width, int height) 84 | { 85 | var bytes = new byte[width * height * 4]; 86 | for (var y = 0; y < height; y++) 87 | { 88 | for (var x = 0; x < width * 4; x += 4) 89 | { 90 | int i = 4 * y * width; 91 | bytes[i + x] = (byte)((x + y) % 256); // R 92 | bytes[i + x + 1] = 0; // G 93 | bytes[i + x + 2] = 0; // B 94 | bytes[i + x + 3] = 255; // A 95 | } 96 | } 97 | 98 | return bytes; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/ZlibStream.Benchmarks/DotNetZlibDeflateStream.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.IO; 6 | using System.IO.Compression; 7 | using System.Runtime.CompilerServices; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace ZlibStream.Benchmarks 11 | { 12 | /// 13 | /// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm. 14 | /// 15 | internal sealed class DotNetZlibDeflateStream : Stream 16 | { 17 | /// 18 | /// The raw stream containing the uncompressed image data. 19 | /// 20 | private readonly Stream rawStream; 21 | 22 | /// 23 | /// Computes the checksum for the data stream. 24 | /// 25 | private readonly Adler32 adler32 = new Adler32(); 26 | 27 | /// 28 | /// A value indicating whether this instance of the given entity has been disposed. 29 | /// 30 | /// if this instance has been disposed; otherwise, . 31 | /// 32 | /// If the entity is disposed, it must not be disposed a second 33 | /// time. The isDisposed field is set the first time the entity 34 | /// is disposed. If the isDisposed field is true, then the Dispose() 35 | /// method will not dispose again. This help not to prolong the entity's 36 | /// life in the Garbage Collector. 37 | /// 38 | private bool isDisposed; 39 | 40 | /// 41 | /// The stream responsible for compressing the input stream. 42 | /// 43 | private DeflateStream deflateStream; 44 | 45 | /// 46 | /// Initializes a new instance of the class. 47 | /// 48 | /// The stream to compress. 49 | /// The compression level. 50 | public DotNetZlibDeflateStream(Stream stream, int compressionLevel) 51 | { 52 | this.rawStream = stream; 53 | 54 | // Write the zlib header : http://tools.ietf.org/html/rfc1950 55 | // CMF(Compression Method and flags) 56 | // This byte is divided into a 4 - bit compression method and a 57 | // 4-bit information field depending on the compression method. 58 | // bits 0 to 3 CM Compression method 59 | // bits 4 to 7 CINFO Compression info 60 | // 61 | // 0 1 62 | // +---+---+ 63 | // |CMF|FLG| 64 | // +---+---+ 65 | int cmf = 0x78; 66 | int flg = 218; 67 | 68 | // http://stackoverflow.com/a/2331025/277304 69 | if (compressionLevel >= 5 && compressionLevel <= 6) 70 | { 71 | flg = 156; 72 | } 73 | else if (compressionLevel >= 3 && compressionLevel <= 4) 74 | { 75 | flg = 94; 76 | } 77 | else if (compressionLevel <= 2) 78 | { 79 | flg = 1; 80 | } 81 | 82 | // Just in case 83 | flg -= ((cmf * 256) + flg) % 31; 84 | 85 | if (flg < 0) 86 | { 87 | flg += 31; 88 | } 89 | 90 | this.rawStream.WriteByte((byte)cmf); 91 | this.rawStream.WriteByte((byte)flg); 92 | 93 | // Initialize the deflate Stream. 94 | CompressionLevel level = CompressionLevel.Optimal; 95 | 96 | if (compressionLevel >= 1 && compressionLevel <= 5) 97 | { 98 | level = CompressionLevel.Fastest; 99 | } 100 | else if (compressionLevel == 0) 101 | { 102 | level = CompressionLevel.NoCompression; 103 | } 104 | 105 | this.deflateStream = new DeflateStream(this.rawStream, level, true); 106 | } 107 | 108 | /// 109 | public override bool CanRead => false; 110 | 111 | /// 112 | public override bool CanSeek => false; 113 | 114 | /// 115 | public override bool CanWrite => true; 116 | 117 | /// 118 | public override long Length => throw new NotSupportedException(); 119 | 120 | /// 121 | public override long Position 122 | { 123 | get => throw new NotSupportedException(); 124 | set => throw new NotSupportedException(); 125 | } 126 | 127 | /// 128 | public override void Flush() => this.deflateStream.Flush(); 129 | 130 | /// 131 | public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); 132 | 133 | /// 134 | public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); 135 | 136 | /// 137 | public override void SetLength(long value) => throw new NotSupportedException(); 138 | 139 | /// 140 | public override void Write(byte[] buffer, int offset, int count) 141 | { 142 | this.deflateStream.Write(buffer, offset, count); 143 | this.adler32.Update(buffer.AsSpan(offset, count)); 144 | } 145 | 146 | /// 147 | protected override void Dispose(bool disposing) 148 | { 149 | if (this.isDisposed) 150 | { 151 | return; 152 | } 153 | 154 | if (disposing) 155 | { 156 | // dispose managed resources 157 | this.deflateStream.Dispose(); 158 | this.deflateStream = null; 159 | 160 | // Add the crc 161 | uint crc = (uint)this.adler32.Value; 162 | this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF)); 163 | this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF)); 164 | this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); 165 | this.rawStream.WriteByte((byte)(crc & 0xFF)); 166 | } 167 | 168 | base.Dispose(disposing); 169 | 170 | // Call the appropriate methods to clean up 171 | // unmanaged resources here. 172 | // Note disposing is done. 173 | this.isDisposed = true; 174 | } 175 | 176 | /// 177 | /// Computes Adler32 checksum for a stream of data. An Adler32 178 | /// checksum is not as reliable as a CRC32 checksum, but a lot faster to 179 | /// compute. 180 | /// 181 | /// 182 | /// The specification for Adler32 may be found in RFC 1950. 183 | /// ZLIB Compressed Data Format Specification version 3.3) 184 | /// 185 | /// 186 | /// From that document: 187 | /// 188 | /// "ADLER32 (Adler-32 checksum) 189 | /// This contains a checksum value of the uncompressed data 190 | /// (excluding any dictionary data) computed according to Adler-32 191 | /// algorithm. This algorithm is a 32-bit extension and improvement 192 | /// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 193 | /// standard. 194 | /// 195 | /// Adler-32 is composed of two sums accumulated per byte: s1 is 196 | /// the sum of all bytes, s2 is the sum of all s1 values. Both sums 197 | /// are done modulo 65521. s1 is initialized to 1, s2 to zero. The 198 | /// Adler-32 checksum is stored as s2*65536 + s1 in most- 199 | /// significant-byte first (network) order." 200 | /// 201 | /// "8.2. The Adler-32 algorithm 202 | /// 203 | /// The Adler-32 algorithm is much faster than the CRC32 algorithm yet 204 | /// still provides an extremely low probability of undetected errors. 205 | /// 206 | /// The modulo on unsigned long accumulators can be delayed for 5552 207 | /// bytes, so the modulo operation time is negligible. If the bytes 208 | /// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position 209 | /// and order sensitive, unlike the first sum, which is just a 210 | /// checksum. That 65521 is prime is important to avoid a possible 211 | /// large class of two-byte errors that leave the check unchanged. 212 | /// (The Fletcher checksum uses 255, which is not prime and which also 213 | /// makes the Fletcher check insensitive to single byte changes 0 - 214 | /// 255.) 215 | /// 216 | /// The sum s1 is initialized to 1 instead of zero to make the length 217 | /// of the sequence part of s2, so that the length does not have to be 218 | /// checked separately. (Any sequence of zeroes has a Fletcher 219 | /// checksum of zero.)" 220 | /// 221 | /// 222 | /// 223 | private sealed class Adler32 224 | { 225 | /// 226 | /// largest prime smaller than 65536 227 | /// 228 | private const uint Base = 65521; 229 | 230 | /// 231 | /// The checksum calculated to far. 232 | /// 233 | private uint checksum; 234 | 235 | /// 236 | /// Initializes a new instance of the class. 237 | /// The checksum starts off with a value of 1. 238 | /// 239 | public Adler32() 240 | { 241 | this.Reset(); 242 | } 243 | 244 | public long Value 245 | { 246 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 247 | get => this.checksum; 248 | } 249 | 250 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 251 | public void Reset() => this.checksum = 1; 252 | 253 | /// 254 | /// Updates the checksum with a byte value. 255 | /// 256 | /// 257 | /// The data value to add. The high byte of the int is ignored. 258 | /// 259 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 260 | public void Update(int value) 261 | { 262 | // We could make a length 1 byte array and call update again, but I 263 | // would rather not have that overhead 264 | uint s1 = this.checksum & 0xFFFF; 265 | uint s2 = this.checksum >> 16; 266 | 267 | s1 = (s1 + ((uint)value & 0xFF)) % Base; 268 | s2 = (s1 + s2) % Base; 269 | 270 | this.checksum = (s2 << 16) + s1; 271 | } 272 | 273 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 274 | public void Update(ReadOnlySpan data) 275 | { 276 | ref byte dataRef = ref MemoryMarshal.GetReference(data); 277 | uint s1 = this.checksum & 0xFFFF; 278 | uint s2 = this.checksum >> 16; 279 | 280 | int count = data.Length; 281 | int offset = 0; 282 | 283 | while (count > 0) 284 | { 285 | // We can defer the modulo operation: 286 | // s1 maximally grows from 65521 to 65521 + 255 * 3800 287 | // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 288 | int n = 3800; 289 | if (n > count) 290 | { 291 | n = count; 292 | } 293 | 294 | count -= n; 295 | while (--n >= 0) 296 | { 297 | s1 += Unsafe.Add(ref dataRef, offset++); 298 | s2 += s1; 299 | } 300 | 301 | s1 %= Base; 302 | s2 %= Base; 303 | } 304 | 305 | this.checksum = (s2 << 16) | s1; 306 | } 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /tests/ZlibStream.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Reflection; 5 | using BenchmarkDotNet.Running; 6 | 7 | namespace ZlibStream.Benchmarks 8 | { 9 | public class Program 10 | { 11 | public static void Main(string[] args) 12 | { 13 | new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/ZlibStream.Benchmarks/ZlibStream.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ZlibStream.Benchmarks 5 | Exe 6 | net6.0;netcoreapp3.1;netcoreapp2.1;net472 7 | false 8 | 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/ZlibStream.Tests/Adler32Tests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using SixLabors.ZlibStream; 6 | using Xunit; 7 | using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; 8 | 9 | namespace ZlibStream.Tests 10 | { 11 | public class Adler32Tests 12 | { 13 | [Theory] 14 | [InlineData(0)] 15 | [InlineData(1)] 16 | [InlineData(2)] 17 | public void ReturnsCorrectWhenEmpty(uint input) 18 | { 19 | Assert.Equal(input, Adler32.Calculate(input, default)); 20 | } 21 | 22 | [Theory] 23 | [InlineData(0)] 24 | [InlineData(8)] 25 | [InlineData(215)] 26 | [InlineData(1024)] 27 | [InlineData(1024 + 15)] 28 | [InlineData(2034)] 29 | [InlineData(4096)] 30 | public void MatchesReference(int length) 31 | { 32 | var data = GetBuffer(length); 33 | var adler = new SharpAdler32(); 34 | adler.Update(data); 35 | 36 | long expected = adler.Value; 37 | long actual = Adler32.Calculate(data); 38 | 39 | Assert.Equal(expected, actual); 40 | } 41 | 42 | private static byte[] GetBuffer(int length) 43 | { 44 | var data = new byte[length]; 45 | new Random(1).NextBytes(data); 46 | 47 | return data; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/ZlibStream.Tests/TestUtilities/Corpus.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace ZlibStream.Tests.TestUtilities 5 | { 6 | /// 7 | /// The Canterbury corpus is a collection of files intended for use as a benchmark for testing 8 | /// lossless data compression algorithms. 9 | /// 10 | /// 11 | public static class Corpus 12 | { 13 | public const string Alice = "alice29.txt"; 14 | public const string Shakespeare = "asyoulik.txt"; 15 | public const string CompressionPointers = "cp.html"; 16 | public const string Fields = "fields.c"; 17 | public const string Grammar = "grammar.lsp"; 18 | public const string Excel = "kennedy.xls"; 19 | public const string Electronic = "lcet10.txt"; 20 | public const string ParadiseLost = "plrabn12.txt"; 21 | public const string CCITT = "ptt5"; 22 | public const string Sum = "sum"; 23 | public const string GnuManual = "xargs.1"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/ZlibStream.Tests/TestUtilities/TestEnvironment.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | namespace ZlibStream.Tests.TestUtilities 10 | { 11 | /// 12 | /// Provides information about the currently running test environment. 13 | /// 14 | public class TestEnvironment 15 | { 16 | private const string ZlibStreamSolutionFileName = "ZlibStream.sln"; 17 | 18 | private const string CorpusRelativePath = @"tests\corpus"; 19 | 20 | private static readonly Lazy SolutionDirectoryFullPathLazy = new Lazy(GetSolutionDirectoryFullPathImpl); 21 | 22 | private static readonly FileInfo TestAssemblyFile = 23 | new FileInfo(typeof(TestEnvironment).GetTypeInfo().Assembly.Location); 24 | 25 | internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; 26 | 27 | /// 28 | /// Gets a value indicating whether test execution runs on CI. 29 | /// 30 | #if ENV_CI 31 | internal static bool RunsOnCI => true; 32 | #else 33 | internal static bool RunsOnCI => false; 34 | #endif 35 | 36 | /// 37 | /// Gets a value indicating whether test execution is running with code coverage testing enabled. 38 | /// 39 | #if ENV_CODECOV 40 | internal static bool RunsWithCodeCoverage => true; 41 | #else 42 | internal static bool RunsWithCodeCoverage => false; 43 | #endif 44 | 45 | /// 46 | /// Gets the full path to the Corpus directory. 47 | /// 48 | public static string CorpusDirectoryFullPath => GetFullPath(CorpusRelativePath); 49 | 50 | private static string GetFullPath(string relativePath) 51 | => Path.Combine(SolutionDirectoryFullPath, relativePath).Replace('\\', Path.DirectorySeparatorChar); 52 | 53 | private static string GetSolutionDirectoryFullPathImpl() 54 | { 55 | DirectoryInfo directory = TestAssemblyFile.Directory; 56 | 57 | while (!directory.EnumerateFiles(ZlibStreamSolutionFileName).Any()) 58 | { 59 | try 60 | { 61 | directory = directory.Parent; 62 | } 63 | catch (Exception ex) 64 | { 65 | throw new DirectoryNotFoundException( 66 | $"Unable to find ZlibStream solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!", 67 | ex); 68 | } 69 | 70 | if (directory is null) 71 | { 72 | throw new DirectoryNotFoundException($"Unable to find ZlibStream solution directory from {TestAssemblyFile}!"); 73 | } 74 | } 75 | 76 | return directory.FullName; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/ZlibStream.Tests/ZlibStream.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0;netcoreapp3.1;netcoreapp2.1;net472 5 | True 6 | True 7 | True 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/ZlibStream.Tests/ZlibStreamTests.Roundtrip.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Six Labors. 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System; 5 | using System.IO; 6 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; 7 | using SixLabors.ZlibStream; 8 | using Xunit; 9 | 10 | namespace ZlibStream.Tests 11 | { 12 | public class ZlibStreamTests 13 | { 14 | [Theory] 15 | [InlineData(CompressionLevel.NoCompression)] 16 | [InlineData(CompressionLevel.Level1)] 17 | [InlineData(CompressionLevel.Level2)] 18 | [InlineData(CompressionLevel.Level3)] 19 | [InlineData(CompressionLevel.Level4)] 20 | [InlineData(CompressionLevel.Level5)] 21 | [InlineData(CompressionLevel.Level6)] 22 | [InlineData(CompressionLevel.Level7)] 23 | [InlineData(CompressionLevel.BestCompression)] 24 | [InlineData(CompressionLevel.DefaultCompression)] 25 | public void EncodeDecode(CompressionLevel level) 26 | { 27 | foreach (CompressionStrategy strategy in (CompressionStrategy[])Enum.GetValues(typeof(CompressionStrategy))) 28 | { 29 | const int count = 2 * 4096 * 4; 30 | byte[] expected = GetBuffer(count); 31 | byte[] reference = new byte[count]; 32 | byte[] actual = new byte[count]; 33 | 34 | using (var compressed = new MemoryStream()) 35 | { 36 | var options = new ZlibOptions { CompressionStrategy = strategy, CompressionLevel = level }; 37 | using (var deflate = new ZlibOutputStream(compressed, options)) 38 | { 39 | deflate.Write(expected, 0, expected.Length); 40 | } 41 | 42 | compressed.Position = 0; 43 | 44 | using (var refInflate = new InflaterInputStream(compressed)) 45 | { 46 | refInflate.IsStreamOwner = false; 47 | refInflate.Read(reference, 0, reference.Length); 48 | } 49 | 50 | compressed.Position = 0; 51 | 52 | using var inflate = new ZlibInputStream(compressed); 53 | inflate.Read(actual, 0, actual.Length); 54 | } 55 | 56 | for (int i = 0; i < expected.Length; i++) 57 | { 58 | byte e = expected[i]; 59 | byte r = reference[i]; 60 | byte a = actual[i]; 61 | 62 | Assert.Equal(e, r); 63 | Assert.Equal(e, a); 64 | } 65 | } 66 | } 67 | 68 | [Theory] 69 | [InlineData(CompressionLevel.NoCompression)] 70 | [InlineData(CompressionLevel.Level1)] 71 | [InlineData(CompressionLevel.Level2)] 72 | [InlineData(CompressionLevel.Level3)] 73 | [InlineData(CompressionLevel.Level4)] 74 | [InlineData(CompressionLevel.Level5)] 75 | [InlineData(CompressionLevel.Level6)] 76 | [InlineData(CompressionLevel.Level7)] 77 | [InlineData(CompressionLevel.BestCompression)] 78 | [InlineData(CompressionLevel.DefaultCompression)] 79 | public void EncodeDecodePerChunk(CompressionLevel level) 80 | { 81 | foreach (CompressionStrategy strategy in (CompressionStrategy[])Enum.GetValues(typeof(CompressionStrategy))) 82 | { 83 | const int count = 2 * 4096 * 4; 84 | const int chunk = 2 * 4096; 85 | byte[] expected = GetBuffer(count); 86 | byte[] reference = new byte[count]; 87 | byte[] actual = new byte[count]; 88 | 89 | using (var compressed = new MemoryStream()) 90 | { 91 | var options = new ZlibOptions { CompressionStrategy = strategy, CompressionLevel = level }; 92 | using (var deflate = new ZlibOutputStream(compressed, options)) 93 | { 94 | for (int i = 0; i < expected.Length; i += chunk) 95 | { 96 | deflate.Write(expected, i, chunk); 97 | } 98 | } 99 | 100 | compressed.Position = 0; 101 | 102 | using (var refInflate = new InflaterInputStream(compressed)) 103 | { 104 | refInflate.IsStreamOwner = false; 105 | refInflate.Read(reference, 0, reference.Length); 106 | } 107 | 108 | compressed.Position = 0; 109 | 110 | using var inflate = new ZlibInputStream(compressed); 111 | for (int i = 0; i < expected.Length; i += chunk) 112 | { 113 | inflate.Read(actual, i, chunk); 114 | } 115 | } 116 | 117 | for (int i = 0; i < expected.Length; i++) 118 | { 119 | byte e = expected[i]; 120 | byte r = reference[i]; 121 | byte a = actual[i]; 122 | 123 | Assert.Equal(e, r); 124 | Assert.Equal(e, a); 125 | } 126 | } 127 | } 128 | 129 | // Used for profiling with Rider. 130 | [Fact] 131 | public void DeflateProfileTest() 132 | { 133 | const int count = 1000 * 1000 * 4; 134 | byte[] expected = GetBuffer(count); 135 | 136 | using var compressed = new MemoryStream(); 137 | using var deflate = new ZlibOutputStream(compressed, CompressionLevel.Level6); 138 | deflate.Write(expected, 0, expected.Length); 139 | } 140 | 141 | [Fact] 142 | public void DeflateMemoryProfileTest() 143 | { 144 | byte[] expected = GetImageBytes(3500, 3500); 145 | 146 | using var compressed = new MemoryStream(); 147 | using var deflate = new ZlibOutputStream(compressed, CompressionLevel.Level1); 148 | deflate.Write(expected, 0, expected.Length); 149 | } 150 | 151 | private static byte[] GetImageBytes(int width, int height) 152 | { 153 | byte[] bytes = new byte[width * height * 4]; 154 | for (int y = 0; y < height; y++) 155 | { 156 | for (int x = 0; x < width * 4; x += 4) 157 | { 158 | int i = 4 * y * width; 159 | bytes[i + x] = (byte)((x + y) % 256); // R 160 | bytes[i + x + 1] = 0; // G 161 | bytes[i + x + 2] = 0; // B 162 | bytes[i + x + 3] = 255; // A 163 | } 164 | } 165 | 166 | return bytes; 167 | } 168 | 169 | private static byte[] GetBuffer(int length) 170 | { 171 | byte[] data = new byte[length]; 172 | new Random(1).NextBytes(data); 173 | 174 | return data; 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /tests/ZlibStream.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "shadowCopy": false, 3 | "methodDisplay": "method", 4 | "diagnosticMessages": true 5 | } 6 | -------------------------------------------------------------------------------- /tests/corpus/cp.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SixLabors/ZlibStream/0bbfaef58d810feec60df80d67f32662b0fd0eab/tests/corpus/cp.html -------------------------------------------------------------------------------- /tests/corpus/grammar.lsp: -------------------------------------------------------------------------------- 1 | ;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*- 2 | 3 | (define-language 4 | :grammar 5 | '(((S $any) -> (S1 $any)) 6 | ((S (Compound $s1 $s2)) -> (S1 $s1) (Conjunction) (S1 $s2)) 7 | 8 | ((S1 (Statement $v)) -> (NP $subj) (VP $subj $tense $v)) 9 | ((S1 (Acknowledge $a)) -> (Acknowledge $a)) 10 | ((S1 (Command $v)) -> (VP Self present $v)) 11 | ((S1 (Question $v)) -> (Aux $tense) (NP $subj) (VP $subj $tense $v)) 12 | ((S1 (Question $v)) -> (Be $tense) (NP $subj) (Be-Arg $subj $tense $v)) 13 | 14 | ((Be-Arg $subj $tense (Occur $tense (loc $subj $loc))) -> 15 | (Loc-Adjunct $tense (loc $subj $loc))) 16 | 17 | ((VP $subj $tense (Occur $tense $v)) -> (VP1 $subj $tense $v)) 18 | ((VP $subj $tense (Occur $tense $v)) -> (Aux $tense)(VP1 $subj present $v)) 19 | 20 | ((VP1 $subj $tense $v) -> (VP2 $subj $tense $v) (Adjunct? $v)) 21 | 22 | ((VP2 $subj $tense ($rel $subj $loc)) -> 23 | (Verb/in $rel $tense)) 24 | ((VP2 $subj $tense ($rel $subj $loc $obj)) -> 25 | (Verb/tr $rel $tense) (NP $obj)) 26 | ((VP2 $subj $tense ($rel $subj $loc $obj $obj2)) -> 27 | (Verb/di $rel $tense) (NP $obj) (NP $obj2)) 28 | ((VP2 $subj $tense (loc $subj $loc)) -> 29 | (Be $tense) (Loc-Adjunct $tense (loc $subj $loc))) 30 | 31 | ((NP $n) -> (Pronoun $n)) 32 | ((NP $n) -> (Article) (Noun $n)) 33 | ((NP $n) -> (Noun $n)) 34 | ((NP ($x $y)) -> (Number $x) (Number $y)) 35 | 36 | ((PP ($prep $n)) -> (Prep $prep) (NP $n)) 37 | ((Adjunct? $v) ->) 38 | ((Adjunct? $v) -> (Loc-Adjunct $tense $v)) 39 | #+Allegro ((Loc-Adjunct $tense ($rel $subj $loc @rest)) -> (PP $loc)) 40 | #+Allegro ((Loc-Adjunct $tense ($rel $subj $loc @rest)) -> (Adjunct $loc)) 41 | #+Lucid ((Loc-Adjunct $tense ($rel $subj $loc . $rest)) -> (PP $loc)) 42 | #+Lucid ((Loc-Adjunct $tense ($rel $subj $loc . $rest)) -> (Adjunct $loc)) 43 | 44 | ) 45 | :lexicon 46 | '( 47 | ((Acknowledge $a) -> (yes true) (no false) (maybe unknown) (huh unparsed)) 48 | ((Adjunct $loc) -> here there (nearby near) near left right up down) 49 | ((Article) -> a an the) 50 | ((Aux $tense) -> (will future) (did past) (do $finite)) 51 | ((Be $tense) -> (am present) (are present) (is present) (be $finite) 52 | (was past) (were past)) 53 | ((Conjunction) -> and --) 54 | ((Noun $n) -> gold Wumpus pit breeze stench glitter nothing) 55 | ((Number $n) -> 0 1 2 3 4 5 6 7 8 9) 56 | ((Prep $prep) -> in at to near) 57 | ((Pronoun $n) -> (you self) (me master) (I master)) 58 | 59 | ((Verb/in $rel $tense) -> (go move $finite) (went move past) 60 | (move move $finite) (move move past) (shoot shoot $finite)) 61 | ((Verb/tr $rel $tense) -> (move carry $finite) (moved carry past) 62 | (carry carry $finite) (carry carried past) 63 | (grab grab $finite) (grab grabbed past) (get grab $finite) 64 | (got grab past) (release release $finite) (release release past) 65 | (drop release $finite) (dropped release past) (shoot shoot-at $finite) 66 | (shot shoot-at past) (kill shoot-at $finite) (killed shoot-at past) 67 | (smell perceive $finite) (feel perceive $finite) (felt perceive past)) 68 | ((Verb/di $rel $tense) -> (bring bring $finite) (brought bring past) 69 | (get bring $finite) (got bring past)) 70 | )) 71 | 72 | (defparameter *sentences* 73 | '((I will shoot the wumpus at 4 4) 74 | (yes) 75 | (You went right -- I will go left) 76 | (carry the gold) 77 | (yes and no) 78 | (did you bring me the gold) 79 | (a breeze is here -- I am near 5 3) 80 | (a stench is in 3 5) 81 | (a pit is nearby) 82 | (is the wumpus near) 83 | (Did you go to 3 8) 84 | (Yes -- Nothing is there) 85 | (Shoot -- Shoot left) 86 | (Kill the wumpus -- shoot up))) 87 | 88 | (defun ss (&optional (sentences *sentences*)) 89 | "Run some test sentences, and count how many were not parsed." 90 | (count-if-not 91 | #'(lambda (s) 92 | (format t "~2&>>> ~(~{~a ~}~)~%" s) 93 | (write (second (parse s)) :pretty t)) 94 | *sentences*)) 95 | -------------------------------------------------------------------------------- /tests/corpus/kennedy.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SixLabors/ZlibStream/0bbfaef58d810feec60df80d67f32662b0fd0eab/tests/corpus/kennedy.xls -------------------------------------------------------------------------------- /tests/corpus/ptt5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SixLabors/ZlibStream/0bbfaef58d810feec60df80d67f32662b0fd0eab/tests/corpus/ptt5 -------------------------------------------------------------------------------- /tests/corpus/sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SixLabors/ZlibStream/0bbfaef58d810feec60df80d67f32662b0fd0eab/tests/corpus/sum -------------------------------------------------------------------------------- /tests/corpus/xargs.1: -------------------------------------------------------------------------------- 1 | .TH XARGS 1L \" -*- nroff -*- 2 | .SH NAME 3 | xargs \- build and execute command lines from standard input 4 | .SH SYNOPSIS 5 | .B xargs 6 | [\-0prtx] [\-e[eof-str]] [\-i[replace-str]] [\-l[max-lines]] 7 | [\-n max-args] [\-s max-chars] [\-P max-procs] [\-\-null] [\-\-eof[=eof-str]] 8 | [\-\-replace[=replace-str]] [\-\-max-lines[=max-lines]] [\-\-interactive] 9 | [\-\-max-chars=max-chars] [\-\-verbose] [\-\-exit] [\-\-max-procs=max-procs] 10 | [\-\-max-args=max-args] [\-\-no-run-if-empty] [\-\-version] [\-\-help] 11 | [command [initial-arguments]] 12 | .SH DESCRIPTION 13 | This manual page 14 | documents the GNU version of 15 | .BR xargs . 16 | .B xargs 17 | reads arguments from the standard input, delimited by blanks (which can be 18 | protected with double or single quotes or a backslash) or newlines, 19 | and executes the 20 | .I command 21 | (default is /bin/echo) one or more times with any 22 | .I initial-arguments 23 | followed by arguments read from standard input. Blank lines on the 24 | standard input are ignored. 25 | .P 26 | .B xargs 27 | exits with the following status: 28 | .nf 29 | 0 if it succeeds 30 | 123 if any invocation of the command exited with status 1-125 31 | 124 if the command exited with status 255 32 | 125 if the command is killed by a signal 33 | 126 if the command cannot be run 34 | 127 if the command is not found 35 | 1 if some other error occurred. 36 | .fi 37 | .SS OPTIONS 38 | .TP 39 | .I "\-\-null, \-0" 40 | Input filenames are terminated by a null character instead of by 41 | whitespace, and the quotes and backslash are not special (every 42 | character is taken literally). Disables the end of file string, which 43 | is treated like any other argument. Useful when arguments might 44 | contain white space, quote marks, or backslashes. The GNU find 45 | \-print0 option produces input suitable for this mode. 46 | .TP 47 | .I "\-\-eof[=eof-str], \-e[eof-str]" 48 | Set the end of file string to \fIeof-str\fR. If the end of file 49 | string occurs as a line of input, the rest of the input is ignored. 50 | If \fIeof-str\fR is omitted, there is no end of file string. If this 51 | option is not given, the end of file string defaults to "_". 52 | .TP 53 | .I "\-\-help" 54 | Print a summary of the options to 55 | .B xargs 56 | and exit. 57 | .TP 58 | .I "\-\-replace[=replace-str], \-i[replace-str]" 59 | Replace occurences of \fIreplace-str\fR in the initial arguments with 60 | names read from standard input. 61 | Also, unquoted blanks do not terminate arguments. 62 | If \fIreplace-str\fR is omitted, it 63 | defaults to "{}" (like for `find \-exec'). Implies \fI\-x\fP and 64 | \fI\-l 1\fP. 65 | .TP 66 | .I "\-\-max-lines[=max-lines], -l[max-lines]" 67 | Use at most \fImax-lines\fR nonblank input lines per command line; 68 | \fImax-lines\fR defaults to 1 if omitted. Trailing blanks cause an 69 | input line to be logically continued on the next input line. Implies 70 | \fI\-x\fR. 71 | .TP 72 | .I "\-\-max-args=max-args, \-n max-args" 73 | Use at most \fImax-args\fR arguments per command line. Fewer than 74 | \fImax-args\fR arguments will be used if the size (see the \-s option) 75 | is exceeded, unless the \-x option is given, in which case \fBxargs\fR 76 | will exit. 77 | .TP 78 | .I "\-\-interactive, \-p" 79 | Prompt the user about whether to run each command line and read a line 80 | from the terminal. Only run the command line if the response starts 81 | with `y' or `Y'. Implies \fI\-t\fR. 82 | .TP 83 | .I "\-\-no-run-if-empty, \-r" 84 | If the standard input does not contain any nonblanks, do not run the 85 | command. Normally, the command is run once even if there is no input. 86 | .TP 87 | .I "\-\-max-chars=max-chars, \-s max-chars" 88 | Use at most \fImax-chars\fR characters per command line, including the 89 | command and initial arguments and the terminating nulls at the ends of 90 | the argument strings. The default is as large as possible, up to 20k 91 | characters. 92 | .TP 93 | .I "\-\-verbose, \-t" 94 | Print the command line on the standard error output before executing 95 | it. 96 | .TP 97 | .I "\-\-version" 98 | Print the version number of 99 | .B xargs 100 | and exit. 101 | .TP 102 | .I "\-\-exit, \-x" 103 | Exit if the size (see the \fI\-s\fR option) is exceeded. 104 | .TP 105 | .I "\-\-max-procs=max-procs, \-P max-procs" 106 | Run up to \fImax-procs\fR processes at a time; the default is 1. If 107 | \fImax-procs\fR is 0, \fBxargs\fR will run as many processes as 108 | possible at a time. Use the \fI\-n\fR option with \fI\-P\fR; 109 | otherwise chances are that only one exec will be done. 110 | .SH "SEE ALSO" 111 | \fBfind\fP(1L), \fBlocate\fP(1L), \fBlocatedb\fP(5L), \fBupdatedb\fP(1) 112 | \fBFinding Files\fP (on-line in Info, or printed) 113 | -------------------------------------------------------------------------------- /tests/coverlet.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | category!=failing 6 | 7 | 8 | 9 | 10 | 11 | lcov 12 | [SixLabors.*]* 13 | true 14 | 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------